@@ -5,12 +5,20 @@ | |||||
1. [熟悉JT1078协议](https://github.com/SmallChi/JT1078) | 1. [熟悉JT1078协议](https://github.com/SmallChi/JT1078) | ||||
2. 了解Http Chunked编码 | 2. 了解Http Chunked编码 | ||||
3. 了解WebSocket消息推送 | 3. 了解WebSocket消息推送 | ||||
4. [了解Flv.js](https://github.com/bilibili/flv.js) | 4. [了解flv.js](https://github.com/bilibili/flv.js) | ||||
5. [了解hls.js](https://github.com/video-dev/hls.js) | |||||
目前只支持Http-Flv、WebSocket-Flv两种方式推流,经过一小时的测试延迟在3秒这样。 | > 注意:暂不支持音频 | ||||
| av | video | audio |test|request| | |||||
| --- | ---| --- |---|---| | |||||
| flv | 😀| ☹ |😀|http-flv、ws-flv| | |||||
| m3u8 | 😀| ☹ |☹|http| | |||||
## NuGet安装 | ## NuGet安装 | ||||
| Package Name | Version | Downloads | | | Package Name | Version |Pre Version|Downloads| | ||||
| --------------------- | -------------------------------------------------- | --------------------------------------------------- | | | --- | ---| --- | --- | | ||||
| Install-Package JT1078.Gateway |  |  | | | Install-Package JT1078.Gateway.Abstractions |  |  |  | | ||||
| Install-Package JT1078.Gateway |  | | | | |||||
| Install-Package JT1078.Gateway.InMemoryMQ |  |  |  | |
@@ -0,0 +1 @@ | |||||
dotnet nuget push .\nupkgs\*.nupkg -k apikey -s https://api.nuget.org/v3/index.json |
@@ -0,0 +1,5 @@ | |||||
dotnet pack .\src\JT1078.Gateway\JT1078.Gateway.csproj --no-build --output .\nupkgs | |||||
dotnet pack .\src\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj --no-build --output .\nupkgs | |||||
dotnet pack .\src\JT1078.Gateway.InMemoryMQ\JT1078.Gateway.InMemoryMQ.csproj --no-build --output .\nupkgs | |||||
pause |
@@ -23,7 +23,9 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="System.Threading.Channels" Version="4.7.1" /> | <PackageReference Include="System.Threading.Channels" Version="4.7.1" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj" /> | <ProjectReference Include="..\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -1,56 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8" ?> | |||||
<!-- | |||||
参考:http://www.cnblogs.com/fuchongjundream/p/3936431.html | |||||
autoReload:自动再配置 | |||||
internalLogFile:可以让NLog把内部的调试和异常信息都写入指定文件里程序没问题了,日志却出了问题。这个该怎么办,到底是哪里不正确了?假如日志本身除了bug该如何解决?这就需要日志排错。把日志的错误信息写入日志。 | |||||
<nlog throwExceptions="true" /> | |||||
<nlog internalLogFile="file.txt" />- 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。 | |||||
<nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - 决定内部日志的级别,级别越高,输出的日志信息越简洁。 | |||||
<nlog internalLogToConsole="false|true" /> - 是否把内部日志输出到标准控制台。 | |||||
<nlog internalLogToConsoleError="false|true" /> - 是否把内部日志输出到标准错误控制台 (stderr)。 | |||||
设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。 | |||||
--> | |||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
autoReload="true" | |||||
internalLogFile="wwwroot/logs/internalLog.txt" | |||||
internalLogLevel="Debug" > | |||||
<variable name="Directory" value="/data/logs/JT1078.Gateway"/> | |||||
<targets> | |||||
<target name="all" xsi:type="File" | |||||
fileName="${Directory}/all/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level}:${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"/> | |||||
<target name="flvencoding" xsi:type="File" | |||||
fileName="${Directory}/flvencoding/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss}:${message}"/> | |||||
<target name="JT1078TcpMessageHandlers" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpMessageHandlers/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="JT1078TcpMessageHandlersHex" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpMessageHandlersHex/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="FlvEncoder" xsi:type="File" | |||||
fileName="${Directory}/FlvEncoder/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="JT1078TcpConnectionHandler" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpConnectionHandler/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="console" xsi:type="ColoredConsole" | |||||
useDefaultRowHighlightingRules="false" | |||||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level} ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"> | |||||
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" /> | |||||
<highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" /> | |||||
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" /> | |||||
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" /> | |||||
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" /> | |||||
</target> | |||||
</targets> | |||||
<rules> | |||||
<logger name="*" minlevel="Error" maxlevel="Fatal" writeTo="all"/> | |||||
<logger name="FlvEncoding" minlevel="Debug" maxlevel="Fatal" writeTo="flvencoding"/> | |||||
<logger name="JT1078.DotNetty.Tcp.Handlers.JT1078TcpConnectionHandler" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpConnectionHandler,console"/> | |||||
<logger name="JT1078TcpMessageHandlers" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpMessageHandlers"/> | |||||
<logger name="JT1078TcpMessageHandlersHex" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpMessageHandlersHex"/> | |||||
<logger name="FlvEncoder" minlevel="Debug" maxlevel="Fatal" writeTo="FlvEncoder"/> | |||||
</rules> | |||||
</nlog> |
@@ -1,48 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8" ?> | |||||
<!-- | |||||
参考:http://www.cnblogs.com/fuchongjundream/p/3936431.html | |||||
autoReload:自动再配置 | |||||
internalLogFile:可以让NLog把内部的调试和异常信息都写入指定文件里程序没问题了,日志却出了问题。这个该怎么办,到底是哪里不正确了?假如日志本身除了bug该如何解决?这就需要日志排错。把日志的错误信息写入日志。 | |||||
<nlog throwExceptions="true" /> | |||||
<nlog internalLogFile="file.txt" />- 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。 | |||||
<nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - 决定内部日志的级别,级别越高,输出的日志信息越简洁。 | |||||
<nlog internalLogToConsole="false|true" /> - 是否把内部日志输出到标准控制台。 | |||||
<nlog internalLogToConsoleError="false|true" /> - 是否把内部日志输出到标准错误控制台 (stderr)。 | |||||
设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。 | |||||
--> | |||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
autoReload="true" | |||||
internalLogFile="wwwroot/logs/internalLog.txt" | |||||
internalLogLevel="Debug" > | |||||
<variable name="Directory" value="${basedir}/wwwroot/logs/JT1078.Gateway"/> | |||||
<targets> | |||||
<target name="all" xsi:type="File" | |||||
fileName="${Directory}/all/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level}:${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"/> | |||||
<target name="JT1078TcpMessageHandlers" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpMessageHandlers/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="JT1078TcpMessageHandlersHex" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpMessageHandlersHex/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="JT1078TcpConnectionHandler" xsi:type="File" | |||||
fileName="${Directory}/JT1078TcpConnectionHandler/${shortdate}.log" | |||||
layout="${date:format=yyyyMMddHHmmss},${message}"/> | |||||
<target name="console" xsi:type="ColoredConsole" | |||||
useDefaultRowHighlightingRules="false" | |||||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level} ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"> | |||||
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" /> | |||||
<highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" /> | |||||
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" /> | |||||
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" /> | |||||
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" /> | |||||
</target> | |||||
</targets> | |||||
<rules> | |||||
<logger name="*" minlevel="Debug" maxlevel="Fatal" writeTo="all,console"/> | |||||
<logger name="JT1078.DotNetty.Tcp.Handlers.JT1078TcpConnectionHandler" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpConnectionHandler,console"/> | |||||
<logger name="JT1078TcpMessageHandlers" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpMessageHandlers,console"/> | |||||
<logger name="JT1078TcpMessageHandlersHex" minlevel="Debug" maxlevel="Fatal" writeTo="JT1078TcpMessageHandlersHex,console"/> | |||||
</rules> | |||||
</nlog> |
@@ -1,18 +0,0 @@ | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Transport.Channels; | |||||
using JT1078.Gateway.Interfaces; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Security.Principal; | |||||
using System.Text; | |||||
namespace JT1078.Gateway.SimpleServer | |||||
{ | |||||
public class CustomHttpMiddleware : IHttpMiddleware | |||||
{ | |||||
public void Next(IChannelHandlerContext ctx, IFullHttpRequest req, IPrincipal principal) | |||||
{ | |||||
Console.WriteLine("CustomHttpMiddleware"); | |||||
} | |||||
} | |||||
} |
@@ -1,107 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using DotNetty.Common.Utilities; | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Handlers.Streams; | |||||
using DotNetty.Transport.Channels; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using System.Net; | |||||
using Microsoft.AspNetCore; | |||||
using Microsoft.AspNetCore.Cors; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace JT1078.Gateway.SimpleServer.HLS | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// -hls_list_size 10 m3u8内部文件内部保留10个集合 | |||||
/// -segment_time 10秒切片 | |||||
/// -hls_wrap 可以让切片文件进行循环 就不会导致产生很多文件了 占用很多空间 | |||||
/// ./ffmpeg -f dshow -i video="USB2.0 PC CAMERA" -hls_wrap 20 -start_number 0 -hls_list_size 10 -f hls "D:\v\sample.m3u8 -segment_time 10" | |||||
/// </summary> | |||||
class FFMPEGHLSHostedService : IHostedService | |||||
{ | |||||
private readonly Process process; | |||||
private const string FileName= "hls_ch1.m3u8"; | |||||
private const string DirectoryName = "hlsvideo"; | |||||
private readonly IWebHost webHost; | |||||
public FFMPEGHLSHostedService() | |||||
{ | |||||
string directoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DirectoryName); | |||||
if (Directory.Exists(directoryPath)) | |||||
{ | |||||
Directory.Delete(directoryPath,true); | |||||
Directory.CreateDirectory(directoryPath); | |||||
} | |||||
else | |||||
{ | |||||
Directory.CreateDirectory(directoryPath); | |||||
} | |||||
string filePath =$"\"{Path.Combine(directoryPath, FileName)}\""; | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -vcodec h264 -hls_wrap 10 -start_number 0 -hls_list_size 10 -f hls {filePath} -segment_time 10", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true | |||||
} | |||||
}; | |||||
webHost= new WebHostBuilder() | |||||
.ConfigureLogging((_, factory) => | |||||
{ | |||||
factory.SetMinimumLevel(LogLevel.Debug); | |||||
factory.AddConsole(); | |||||
}) | |||||
.ConfigureAppConfiguration((hostingContext, config) => | |||||
{ | |||||
config.SetBasePath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"HLS")); | |||||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); | |||||
}) | |||||
.UseKestrel(ksOptions => | |||||
{ | |||||
ksOptions.ListenAnyIP(5001); | |||||
}) | |||||
.UseWebRoot(AppDomain.CurrentDomain.BaseDirectory) | |||||
.UseStartup<Startup>() | |||||
.Build(); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
webHost.RunAsync(cancellationToken); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
process.Kill(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
webHost.WaitForShutdownAsync(); | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,25 +0,0 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.StaticFiles; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Gateway.SimpleServer.HLS | |||||
{ | |||||
public class Startup | |||||
{ | |||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) | |||||
{ | |||||
//mime | |||||
//https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/DeployingHTTPLiveStreaming/DeployingHTTPLiveStreaming.html | |||||
var Provider = new FileExtensionContentTypeProvider(); | |||||
Provider.Mappings[".m3u8"] = "application/x-mpegURL,vnd.apple.mpegURL"; | |||||
Provider.Mappings[".ts"] = "video/MP2T"; | |||||
app.UseStaticFiles(new StaticFileOptions() | |||||
{ | |||||
ContentTypeProvider = Provider | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -1,9 +0,0 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Debug" //Warning | |||||
} | |||||
}, | |||||
"AllowedHosts": "*", | |||||
"AllowedOrigins": "*" | |||||
} |
@@ -1,25 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title>hls demo</title> | |||||
<script src="hls.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<h6>https://poanchen.github.io/blog/2016/11/17/how-to-play-mp4-video-using-hls</h6> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script type="text/javascript"> | |||||
var video = document.getElementById("player"); | |||||
var videoSrcHls = "/hlsvideo/hls_ch1.m3u8"; | |||||
if (Hls.isSupported()) { | |||||
var hls = new Hls(); | |||||
hls.loadSource(videoSrcHls); | |||||
hls.attachMedia(video); | |||||
hls.on(Hls.Events.MANIFEST_PARSED, function () { | |||||
video.play(); | |||||
}); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -1,134 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using DotNetty.Common.Utilities; | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Handlers.Streams; | |||||
using DotNetty.Transport.Channels; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using JT1078.Gateway.Extensions; | |||||
using JT1078.Gateway.Session; | |||||
namespace JT1078.Gateway.SimpleServer.HTTPFLV | |||||
{ | |||||
class FFMPEGHTTPFLVHostedService : IHostedService,IDisposable | |||||
{ | |||||
private readonly Process process; | |||||
private readonly NamedPipeServerStream pipeServerOut; | |||||
private const string PipeNameOut = "demo1serverout"; | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
/// <summary> | |||||
/// 需要缓存flv的第一包数据,当新用户进来先推送第一包的数据 | |||||
/// </summary> | |||||
private byte[] flvFirstPackage; | |||||
private ConcurrentDictionary<string, byte> exists = new ConcurrentDictionary<string, byte>(); | |||||
public FFMPEGHTTPFLVHostedService(JT1078HttpSessionManager jT1078HttpSessionManager) | |||||
{ | |||||
pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,10240,10240); | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -c copy -f flv -vcodec h264 -y \\.\pipe\{PipeNameOut}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true | |||||
} | |||||
}; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
Task.Run(() => | |||||
{ | |||||
while (true) | |||||
{ | |||||
try | |||||
{ | |||||
Console.WriteLine("IsConnected>>>" + pipeServerOut.IsConnected); | |||||
if (pipeServerOut.IsConnected) | |||||
{ | |||||
if (pipeServerOut.CanRead) | |||||
{ | |||||
Span<byte> v1 = new byte[2048]; | |||||
var length = pipeServerOut.Read(v1); | |||||
var realValue = v1.Slice(0, length).ToArray(); | |||||
if (realValue.Length <= 0) continue; | |||||
if (flvFirstPackage == null) | |||||
{ | |||||
flvFirstPackage = realValue; | |||||
} | |||||
if (jT1078HttpSessionManager.GetAll().Count() > 0) | |||||
{ | |||||
foreach (var session in jT1078HttpSessionManager.GetAll()) | |||||
{ | |||||
if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | |||||
{ | |||||
session.SendHttpFirstChunkAsync(flvFirstPackage); | |||||
exists.TryAdd(session.Channel.Id.AsShortText(), 0); | |||||
} | |||||
session.SendHttpOtherChunkAsync(realValue); | |||||
} | |||||
} | |||||
//Console.WriteLine(JsonConvert.SerializeObject(realValue)+"-"+ length.ToString()); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (!pipeServerOut.IsConnected) | |||||
{ | |||||
Console.WriteLine("WaitForConnection Star..."); | |||||
pipeServerOut.WaitForConnectionAsync().Wait(300); | |||||
Console.WriteLine("WaitForConnection End..."); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
} | |||||
} | |||||
}); | |||||
return Task.CompletedTask; | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
pipeServerOut.Dispose(); | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
process.Kill(); | |||||
pipeServerOut.Flush(); | |||||
pipeServerOut.Close(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,26 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title></title> | |||||
<script src="flv.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script> | |||||
if (flvjs.isSupported()) { | |||||
var player = document.getElementById('player'); | |||||
var flvPlayer = flvjs.createPlayer({ | |||||
type: 'flv', | |||||
isLive: true, | |||||
url: "http://127.0.0.1:1818/demo.flv?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
}); | |||||
flvPlayer.attachMediaElement(player); | |||||
flvPlayer.load(); | |||||
flvPlayer.play(); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -1,54 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using JT1078.Gateway.Interfaces; | |||||
using JT1078.Gateway.Metadata; | |||||
using JT1078.Gateway.SimpleServer.JT1078WSFlv; | |||||
using JT1078.Protocol; | |||||
using Microsoft.Extensions.Logging; | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace JT1078.Gateway.SimpleServer.Handlers | |||||
{ | |||||
public class JT1078TcpMessageHandlers : IJT1078TcpMessageHandlers | |||||
{ | |||||
private readonly ILogger logger; | |||||
private readonly ILogger hexLogger; | |||||
private readonly JT1078WSFlvDataService jT1078WSFlvDataService; | |||||
public JT1078TcpMessageHandlers( | |||||
JT1078WSFlvDataService jT1078WSFlvDataServic, | |||||
ILoggerFactory loggerFactory) | |||||
{ | |||||
this.jT1078WSFlvDataService = jT1078WSFlvDataServic; | |||||
logger = loggerFactory.CreateLogger("JT1078TcpMessageHandlers"); | |||||
hexLogger = loggerFactory.CreateLogger("JT1078TcpMessageHandlersHex"); | |||||
//var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "h264", "demo.h264"); | |||||
//if (!File.Exists(path)) | |||||
//{ | |||||
// File.Create(path); | |||||
//} | |||||
} | |||||
public Task<JT1078Response> Processor(JT1078Request request) | |||||
{ | |||||
//var path=Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "h264", $"demo.h264"); | |||||
//using (FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write)) | |||||
//{ | |||||
// fs.Write(request.Src); | |||||
// fs.Close(); | |||||
//} | |||||
logger.LogInformation(JsonConvert.SerializeObject(request.Package)); | |||||
//hexLogger.LogInformation($"{request.Package.SIM},{request.Package.Label3.DataType.ToString()},{request.Package.LastFrameInterval},{request.Package.LastIFrameInterval},{request.Package.Timestamp},{request.Package.SN},{request.Package.LogicChannelNumber},{request.Package.Label3.SubpackageType.ToString()},{ByteBufferUtil.HexDump(request.Src)}"); | |||||
hexLogger.LogInformation($"{request.Package.SIM},{request.Package.SN},{request.Package.LogicChannelNumber},{request.Package.Label3.DataType.ToString()},{request.Package.Label3.SubpackageType.ToString()},{ByteBufferUtil.HexDump(request.Src)}"); | |||||
var mergePackage = JT1078Serializer.Merge(request.Package); | |||||
if (mergePackage != null) | |||||
{ | |||||
jT1078WSFlvDataService.JT1078Packages.Add(mergePackage); | |||||
} | |||||
return Task.FromResult<JT1078Response>(default); | |||||
} | |||||
} | |||||
} |
@@ -1,25 +0,0 @@ | |||||
using JT1078.Gateway.Interfaces; | |||||
using JT1078.Gateway.Metadata; | |||||
using JT1078.Protocol; | |||||
using Microsoft.Extensions.Logging; | |||||
using Newtonsoft.Json; | |||||
using System.Threading.Tasks; | |||||
namespace JT1078.Gateway.SimpleServer.Handlers | |||||
{ | |||||
public class JT1078UdpMessageHandlers : IJT1078UdpMessageHandlers | |||||
{ | |||||
private readonly ILogger<JT1078UdpMessageHandlers> logger; | |||||
public JT1078UdpMessageHandlers(ILoggerFactory loggerFactory) | |||||
{ | |||||
logger = loggerFactory.CreateLogger<JT1078UdpMessageHandlers>(); | |||||
} | |||||
public Task<JT1078Response> Processor(JT1078Request request) | |||||
{ | |||||
logger.LogDebug(JsonConvert.SerializeObject(request.Package)); | |||||
return Task.FromResult<JT1078Response>(default); | |||||
} | |||||
} | |||||
} |
@@ -1,24 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Gateway.SimpleServer | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// .\ffmpeg -list_options true -f dshow -i video = "USB2.0 PC CAMERA" | |||||
/// .\ffmpeg -f dshow -i video = "USB2.0 PC CAMERA" -vcodec libx264 "D:\mycamera.flv" | |||||
/// | |||||
/// .\ffmpeg -f dshow -i video = "USB2.0 PC CAMERA" - c copy -f flv -vcodec h264 "rtmp://127.0.0.1/living/streamName" | |||||
/// .\ffplay rtmp://127.0.0.1/living/streamName | |||||
/// | |||||
/// .\ffmpeg -f dshow -i video = "USB2.0 PC CAMERA" - c copy -f -y flv -vcodec h264 "pipe://demoserverout" | |||||
/// | |||||
/// ref:https://www.cnblogs.com/lidabo/p/8662955.html | |||||
/// </summary> | |||||
public static class HardwareCamera | |||||
{ | |||||
public const string CameraName = "\"USB2.0 PC CAMERA\""; | |||||
public const string RTMPURL = "rtmp://127.0.0.1/living/streamName"; | |||||
} | |||||
} |
@@ -1,80 +0,0 @@ | |||||
using System; | |||||
namespace JT1078.Gateway.SimpleServer | |||||
{ | |||||
public static partial class BinaryExtensions | |||||
{ | |||||
public static string ToHexString(this byte[] source) | |||||
{ | |||||
return HexUtil.DoHexDump(source, 0, source.Length).ToUpper(); | |||||
} | |||||
/// <summary> | |||||
/// 16进制字符串转16进制数组 | |||||
/// </summary> | |||||
/// <param name="hexString"></param> | |||||
/// <param name="separator"></param> | |||||
/// <returns></returns> | |||||
public static byte[] ToHexBytes(this string hexString) | |||||
{ | |||||
hexString = hexString.Replace(" ", ""); | |||||
byte[] buf = new byte[hexString.Length / 2]; | |||||
ReadOnlySpan<char> readOnlySpan = hexString.AsSpan(); | |||||
for (int i = 0; i < hexString.Length; i++) | |||||
{ | |||||
if (i % 2 == 0) | |||||
{ | |||||
buf[i / 2] = Convert.ToByte(readOnlySpan.Slice(i, 2).ToString(), 16); | |||||
} | |||||
} | |||||
return buf; | |||||
} | |||||
} | |||||
public static class HexUtil | |||||
{ | |||||
static readonly char[] HexdumpTable = new char[256 * 4]; | |||||
static HexUtil() | |||||
{ | |||||
char[] digits = "0123456789ABCDEF".ToCharArray(); | |||||
for (int i = 0; i < 256; i++) | |||||
{ | |||||
HexdumpTable[i << 1] = digits[(int)((uint)i >> 4 & 0x0F)]; | |||||
HexdumpTable[(i << 1) + 1] = digits[i & 0x0F]; | |||||
} | |||||
} | |||||
public static string DoHexDump(ReadOnlySpan<byte> buffer, int fromIndex, int length) | |||||
{ | |||||
if (length == 0) | |||||
{ | |||||
return ""; | |||||
} | |||||
int endIndex = fromIndex + length; | |||||
var buf = new char[length << 1]; | |||||
int srcIdx = fromIndex; | |||||
int dstIdx = 0; | |||||
for (; srcIdx < endIndex; srcIdx++, dstIdx += 2) | |||||
{ | |||||
Array.Copy(HexdumpTable, buffer[srcIdx] << 1, buf, dstIdx, 2); | |||||
} | |||||
return new string(buf); | |||||
} | |||||
public static string DoHexDump(byte[] array, int fromIndex, int length) | |||||
{ | |||||
if (length == 0) | |||||
{ | |||||
return ""; | |||||
} | |||||
int endIndex = fromIndex + length; | |||||
var buf = new char[length << 1]; | |||||
int srcIdx = fromIndex; | |||||
int dstIdx = 0; | |||||
for (; srcIdx < endIndex; srcIdx++, dstIdx += 2) | |||||
{ | |||||
Array.Copy(HexdumpTable, (array[srcIdx] & 0xFF) << 1, buf, dstIdx, 2); | |||||
} | |||||
return new string(buf); | |||||
} | |||||
} | |||||
} |
@@ -1,26 +0,0 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<OutputType>Exe</OutputType> | |||||
<TargetFramework>netcoreapp3.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="JT1078.Flv" Version="1.0.0-preview4" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0" /> | |||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.0" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\JT1078.Gateway\JT1078.Gateway.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -1,17 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol; | |||||
namespace JT1078.Gateway.SimpleServer.JT1078WSFlv | |||||
{ | |||||
public class JT1078WSFlvDataService | |||||
{ | |||||
public JT1078WSFlvDataService() | |||||
{ | |||||
JT1078Packages = new BlockingCollection<JT1078Package>(); | |||||
} | |||||
public BlockingCollection<JT1078Package> JT1078Packages { get; set; } | |||||
} | |||||
} |
@@ -1,108 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using JT1078.Flv; | |||||
using Microsoft.Extensions.Logging; | |||||
using JT1078.Gateway.Session; | |||||
using JT1078.Gateway.Extensions; | |||||
namespace JT1078.Gateway.SimpleServer.JT1078WSFlv | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
class JT1078WSFlvHostedService : IHostedService | |||||
{ | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
private ConcurrentDictionary<string, byte> exists = new ConcurrentDictionary<string, byte>(); | |||||
private readonly JT1078WSFlvDataService jT1078WSFlvDataService; | |||||
private readonly FlvEncoder FlvEncoder; | |||||
private readonly ILogger logger; | |||||
private readonly ILogger flvEncodingLogger; | |||||
public JT1078WSFlvHostedService( | |||||
ILoggerFactory loggerFactory, | |||||
JT1078WSFlvDataService jT1078WSFlvDataServic, | |||||
JT1078HttpSessionManager jT1078HttpSessionManager) | |||||
{ | |||||
logger = loggerFactory.CreateLogger("JT1078WSFlvHostedService"); | |||||
flvEncodingLogger = loggerFactory.CreateLogger("FlvEncoding"); | |||||
this.jT1078WSFlvDataService = jT1078WSFlvDataServic; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
FlvEncoder = new FlvEncoder(loggerFactory); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
Task.Run(() => | |||||
{ | |||||
try | |||||
{ | |||||
Stopwatch stopwatch = new Stopwatch(); | |||||
foreach (var item in jT1078WSFlvDataService.JT1078Packages.GetConsumingEnumerable()) | |||||
{ | |||||
stopwatch.Start(); | |||||
var flv3 = FlvEncoder.CreateFlvFrame(item); | |||||
stopwatch.Stop(); | |||||
if(flvEncodingLogger.IsEnabled(LogLevel.Debug)) | |||||
{ | |||||
long times = stopwatch.ElapsedMilliseconds; | |||||
flvEncodingLogger.LogDebug($"flv encoding {times.ToString()}ms"); | |||||
} | |||||
stopwatch.Reset(); | |||||
if (flv3 == null) continue; | |||||
if (jT1078HttpSessionManager.GetAll().Count() > 0) | |||||
{ | |||||
foreach (var session in jT1078HttpSessionManager.GetAll()) | |||||
{ | |||||
if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | |||||
{ | |||||
exists.TryAdd(session.Channel.Id.AsShortText(), 0); | |||||
string key = item.GetKey(); | |||||
//ws-flv | |||||
//session.SendBinaryWebSocketAsync(FlvEncoder.GetFirstFlvFrame(key, flv3)); | |||||
//http-flv | |||||
var buffer = FlvEncoder.GetFirstFlvFrame(key, flv3); | |||||
if (buffer != null) | |||||
{ | |||||
flvEncodingLogger.LogDebug(JsonConvert.SerializeObject(buffer)); | |||||
session.SendHttpFirstChunkAsync(buffer); | |||||
} | |||||
continue; | |||||
} | |||||
//ws-flv | |||||
//session.SendBinaryWebSocketAsync(flv3); | |||||
//http-flv | |||||
session.SendHttpOtherChunkAsync(flv3); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
} | |||||
}); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,41 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title></title> | |||||
<script src="flv.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script> | |||||
if (flvjs.isSupported()) { | |||||
var player = document.getElementById('player'); | |||||
var flvPlayer = flvjs.createPlayer({ | |||||
type: 'flv', | |||||
isLive: true, | |||||
url: "ws://127.0.0.1:1818/jt1078live?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
}); | |||||
flvPlayer.attachMediaElement(player); | |||||
flvPlayer.load(); | |||||
flvPlayer.play(); | |||||
function componentDidMount() { | |||||
this.cleanBuff = setInterval(function () { | |||||
let buffered = player.buffered | |||||
console.log("start...") | |||||
if (buffered.length > 0) { | |||||
let end = buffered.end(0) | |||||
if (end - player.currentTime > 0.15) { | |||||
player.currentTime = end - 0.1; | |||||
console.log("exe... start") | |||||
} | |||||
} | |||||
console.log("end...") | |||||
}, 3 * 10 * 1000) | |||||
}; | |||||
componentDidMount(); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -1,86 +0,0 @@ | |||||
using JT1078.Gateway.SimpleServer.Handlers; | |||||
using JT1078.Gateway.SimpleServer.JT1078WSFlv; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Newtonsoft.Json; | |||||
using Newtonsoft.Json.Converters; | |||||
using NLog.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Diagnostics; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT1078.Gateway.SimpleServer | |||||
{ | |||||
class Program | |||||
{ | |||||
static Program() | |||||
{ | |||||
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings(); | |||||
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() => | |||||
{ | |||||
setting.Converters.Add(new StringEnumConverter()); | |||||
return setting; | |||||
}); | |||||
} | |||||
static async Task Main(string[] args) | |||||
{ | |||||
//3031636481E2108801123456781001100000016BB392CA7C02800028002E0000000161E1A2BF0098CFC0EE1E17283407788E39A403FDDBD1D546BFB063013F59AC34C97A021AB96A28A42C08 | |||||
var serverHostBuilder = new HostBuilder() | |||||
.ConfigureAppConfiguration((hostingContext, config) => | |||||
{ | |||||
config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); | |||||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); | |||||
}) | |||||
.ConfigureLogging((context, logging) => | |||||
{ | |||||
if (Environment.OSVersion.Platform == PlatformID.Unix) | |||||
{ | |||||
NLog.LogManager.LoadConfiguration("Configs/nlog.unix.config"); | |||||
} | |||||
else | |||||
{ | |||||
NLog.LogManager.LoadConfiguration("Configs/nlog.win.config"); | |||||
} | |||||
logging.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true }); | |||||
logging.SetMinimumLevel(LogLevel.Trace); | |||||
}) | |||||
.ConfigureServices((hostContext, services) => | |||||
{ | |||||
services.AddSingleton<JT1078WSFlvDataService>(); | |||||
services.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||||
services.AddJT1078Gateway(hostContext.Configuration) | |||||
.AddTcpHost() | |||||
.Replace<JT1078TcpMessageHandlers>() | |||||
.Builder() | |||||
//.AddJT1078UdpHost() | |||||
//.Replace<JT1078UdpMessageHandlers>() | |||||
// .Builder() | |||||
.AddHttpHost() | |||||
//.UseHttpMiddleware<CustomHttpMiddleware>() | |||||
//.Builder() | |||||
; | |||||
//使用ffmpeg工具 | |||||
//1.success | |||||
//services.AddHostedService<FFMPEGRTMPHostedService>(); | |||||
//2.success | |||||
//services.AddHostedService<FFMPEGHTTPFLVHostedService>(); | |||||
//3.success | |||||
//services.AddHostedService<FFMPEGWSFLVPHostedService>(); | |||||
//4.success | |||||
//http://127.0.0.1:5001/HLS/hls.html | |||||
//services.AddHostedService<FFMPEGHLSHostedService>(); | |||||
services.AddHostedService<JT1078WSFlvHostedService>(); | |||||
}); | |||||
await serverHostBuilder.RunConsoleAsync(); | |||||
} | |||||
} | |||||
} |
@@ -1,58 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
namespace JT1078.Gateway.SimpleServer.RTMP | |||||
{ | |||||
/// <summary> | |||||
/// 1.部署 RTMP 服务器 https://github.com/a1q123456/Harmonic | |||||
/// 2.使用ffplay播放器查看 ./ffplay rtmp://127.0.0.1/living/streamName | |||||
/// ref: | |||||
/// https://stackoverflow.com/questions/32157774/ffmpeg-output-pipeing-to-named-windows-pipe | |||||
/// https://mathewsachin.github.io/blog/2017/07/28/ffmpeg-pipe-csharp.html | |||||
/// https://csharp.hotexamples.com/examples/-/NamedPipeServerStream/-/php-namedpipeserverstream-class-examples.html | |||||
/// | |||||
/// ffmpeg pipe作为客户端 | |||||
/// NamedPipeServerStream作为服务端 | |||||
/// </summary> | |||||
class FFMPEGRTMPHostedService : IHostedService | |||||
{ | |||||
private readonly Process process; | |||||
public FFMPEGRTMPHostedService() | |||||
{ | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -c copy -f flv -vcodec h264 {HardwareCamera.RTMPURL}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true | |||||
} | |||||
}; | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Kill(); | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,129 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using JT1078.Gateway.Session; | |||||
using JT1078.Gateway.Extensions; | |||||
namespace JT1078.Gateway.SimpleServer.WSFLV | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// </summary> | |||||
class FFMPEGWSFLVHostedService : IHostedService,IDisposable | |||||
{ | |||||
private readonly Process process; | |||||
private readonly NamedPipeServerStream pipeServerOut; | |||||
private const string PipeNameOut = "demo2serverout"; | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
/// <summary> | |||||
/// 需要缓存flv的第一包数据,当新用户进来先推送第一包的数据 | |||||
/// </summary> | |||||
private byte[] flvFirstPackage; | |||||
private ConcurrentDictionary<string,byte> exists = new ConcurrentDictionary<string, byte>(); | |||||
public FFMPEGWSFLVHostedService( | |||||
JT1078HttpSessionManager jT1078HttpSessionManager) | |||||
{ | |||||
pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,102400,102400); | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -c copy -f flv -vcodec h264 -y \\.\pipe\{PipeNameOut}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true, | |||||
} | |||||
}; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
pipeServerOut.Dispose(); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
Task.Run(() => | |||||
{ | |||||
while (true) | |||||
{ | |||||
try | |||||
{ | |||||
Console.WriteLine("IsConnected>>>" + pipeServerOut.IsConnected); | |||||
if (pipeServerOut.IsConnected) | |||||
{ | |||||
if (pipeServerOut.CanRead) | |||||
{ | |||||
Span<byte> v1 = new byte[2048]; | |||||
var length = pipeServerOut.Read(v1); | |||||
var realValue = v1.Slice(0, length).ToArray(); | |||||
if (realValue.Length <= 0) continue; | |||||
if (flvFirstPackage == null) | |||||
{ | |||||
flvFirstPackage = realValue; | |||||
} | |||||
if (jT1078HttpSessionManager.GetAll().Count() > 0) | |||||
{ | |||||
foreach (var session in jT1078HttpSessionManager.GetAll()) | |||||
{ | |||||
if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | |||||
{ | |||||
session.SendBinaryWebSocketAsync(flvFirstPackage); | |||||
exists.TryAdd(session.Channel.Id.AsShortText(), 0); | |||||
} | |||||
session.SendBinaryWebSocketAsync(realValue); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (!pipeServerOut.IsConnected) | |||||
{ | |||||
Console.WriteLine("WaitForConnection Star..."); | |||||
pipeServerOut.WaitForConnectionAsync().Wait(300); | |||||
Console.WriteLine("WaitForConnection End..."); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
} | |||||
} | |||||
}); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
process.Kill(); | |||||
pipeServerOut.Flush(); | |||||
pipeServerOut.Close(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,26 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title></title> | |||||
<script src="flv.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script> | |||||
if (flvjs.isSupported()) { | |||||
var player = document.getElementById('player'); | |||||
var flvPlayer = flvjs.createPlayer({ | |||||
type: 'flv', | |||||
isLive: true, | |||||
url: "ws://127.0.0.1:1818/jt1078live?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
}); | |||||
flvPlayer.attachMediaElement(player); | |||||
flvPlayer.load(); | |||||
flvPlayer.play(); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -1,23 +0,0 @@ | |||||
{ | |||||
"Logging": { | |||||
"IncludeScopes": false, | |||||
"Debug": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
}, | |||||
"Console": { | |||||
"LogLevel": { | |||||
"Default": "Trace" | |||||
} | |||||
} | |||||
}, | |||||
"JT1078Configuration": { | |||||
"TcpPort": 1808, | |||||
"UdpPort": 1808, | |||||
"HttpPort": 1818, | |||||
"RemoteServerOptions": { | |||||
} | |||||
} | |||||
} |
@@ -15,7 +15,8 @@ | |||||
var flvPlayer = flvjs.createPlayer({ | var flvPlayer = flvjs.createPlayer({ | ||||
type: 'flv', | type: 'flv', | ||||
isLive: true, | isLive: true, | ||||
url: "ws://127.0.0.1:15555/?sim=11234567810&channel=1&token=123456" | //url: "http://127.0.0.1:15555/?sim=1901305037&channel=3&token=123456" | ||||
//url: "ws://127.0.0.1:15555/?sim=11234567810&channel=1&token=123456" | |||||
}); | }); | ||||
flvPlayer.attachMediaElement(player); | flvPlayer.attachMediaElement(player); | ||||
flvPlayer.load(); | flvPlayer.load(); | ||||
@@ -14,6 +14,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static async ValueTask Http401(this HttpListenerContext context) | public static async ValueTask Http401(this HttpListenerContext context) | ||||
{ | { | ||||
byte[] b = Encoding.UTF8.GetBytes("auth error"); | byte[] b = Encoding.UTF8.GetBytes("auth error"); | ||||
context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; | context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; | ||||
context.Response.KeepAlive = false; | context.Response.KeepAlive = false; | ||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
@@ -26,6 +28,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static async ValueTask Http400(this HttpListenerContext context) | public static async ValueTask Http400(this HttpListenerContext context) | ||||
{ | { | ||||
byte[] b = Encoding.UTF8.GetBytes($"sim and channel parameter required."); | byte[] b = Encoding.UTF8.GetBytes($"sim and channel parameter required."); | ||||
context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest; | context.Response.StatusCode = (int)HttpStatusCode.BadRequest; | ||||
context.Response.KeepAlive = false; | context.Response.KeepAlive = false; | ||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
@@ -37,6 +41,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static void Http404(this HttpListenerContext context) | public static void Http404(this HttpListenerContext context) | ||||
{ | { | ||||
context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound; | context.Response.StatusCode = (int)HttpStatusCode.NotFound; | ||||
context.Response.KeepAlive = false; | context.Response.KeepAlive = false; | ||||
context.Response.OutputStream.Close(); | context.Response.OutputStream.Close(); | ||||
@@ -46,6 +52,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static async ValueTask Http500(this HttpListenerContext context) | public static async ValueTask Http500(this HttpListenerContext context) | ||||
{ | { | ||||
byte[] b = Encoding.UTF8.GetBytes("inner error"); | byte[] b = Encoding.UTF8.GetBytes("inner error"); | ||||
context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | ||||
context.Response.KeepAlive = false; | context.Response.KeepAlive = false; | ||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
@@ -57,6 +65,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static async ValueTask HttpSendFirstChunked(this JT1078HttpContext context, ReadOnlyMemory<byte> buffer) | public static async ValueTask HttpSendFirstChunked(this JT1078HttpContext context, ReadOnlyMemory<byte> buffer) | ||||
{ | { | ||||
context.Context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Context.Response.StatusCode = (int)HttpStatusCode.OK; | context.Context.Response.StatusCode = (int)HttpStatusCode.OK; | ||||
context.Context.Response.SendChunked = true; | context.Context.Response.SendChunked = true; | ||||
await context.Context.Response.OutputStream.WriteAsync(buffer); | await context.Context.Response.OutputStream.WriteAsync(buffer); | ||||
@@ -71,6 +81,8 @@ namespace JT1078.Gateway.Extensions | |||||
public static async ValueTask HttpClose(this JT1078HttpContext context) | public static async ValueTask HttpClose(this JT1078HttpContext context) | ||||
{ | { | ||||
byte[] b = Encoding.UTF8.GetBytes("close"); | byte[] b = Encoding.UTF8.GetBytes("close"); | ||||
context.Context.Response.AddHeader("Access-Control-Allow-Headers", "*"); | |||||
context.Context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); | |||||
context.Context.Response.StatusCode = (int)HttpStatusCode.OK; | context.Context.Response.StatusCode = (int)HttpStatusCode.OK; | ||||
context.Context.Response.KeepAlive = false; | context.Context.Response.KeepAlive = false; | ||||
context.Context.Response.ContentLength64 = b.Length; | context.Context.Response.ContentLength64 = b.Length; | ||||
@@ -1,5 +1,5 @@ | |||||
<Project> | <Project> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<JT1078PackageVersion>1.0.0-preview3</JT1078PackageVersion> | <JT1078PackageVersion>1.0.0-preview2</JT1078PackageVersion> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
</Project> | </Project> |