@@ -15,7 +15,7 @@ | |||||
| --- | ---| --- |---|---| | | --- | ---| --- |---|---| | ||||
| flv | 😀| ☹ |😀|http-flv、ws-flv| | | flv | 😀| ☹ |😀|http-flv、ws-flv| | ||||
| m3u8 | 😀| ☹ |😀|http| | | m3u8 | 😀| ☹ |😀|http| | ||||
| fmp4 | 😀| ☹ |☹|http-fmp4、ws-fmp4| | |||||
| fmp4 | 😀| ☹ |😀(部分设备可用)|http-fmp4[X]、ws-fmp4[✔]| | |||||
## NuGet安装 | ## NuGet安装 | ||||
@@ -41,8 +41,8 @@ | |||||
</targets> | </targets> | ||||
<rules> | <rules> | ||||
<logger name="*" minlevel="Error" writeTo="all,console"/> | <logger name="*" minlevel="Error" writeTo="all,console"/> | ||||
<logger name="JT1078.Gateway.JT1078TcpServer" minlevel="Trace" writeTo="JT1078TcpServer,console"/> | |||||
<logger name="JT1078.Gateway.JT1078UdpServer" minlevel="Trace" writeTo="JT1078UdpServer,console"/> | |||||
<logger name="JT1078.Gateway.JT1078TcpServer" minlevel="Info" writeTo="JT1078TcpServer,console"/> | |||||
<logger name="JT1078.Gateway.JT1078UdpServer" minlevel="Info" writeTo="JT1078UdpServer,console"/> | |||||
<logger name="JT1078.Gateway.JT1078Logging" minlevel="Trace" writeTo="JT1078Logging,console"/> | <logger name="JT1078.Gateway.JT1078Logging" minlevel="Trace" writeTo="JT1078Logging,console"/> | ||||
</rules> | </rules> | ||||
</nlog> | </nlog> |
@@ -29,7 +29,7 @@ namespace JT1078.Gateway.TestNormalHosting | |||||
}) | }) | ||||
.ConfigureLogging((context, logging) => | .ConfigureLogging((context, logging) => | ||||
{ | { | ||||
logging.SetMinimumLevel(LogLevel.Trace); | |||||
//logging.SetMinimumLevel(LogLevel.Trace); | |||||
Console.WriteLine($"Environment.OSVersion.Platform:{Environment.OSVersion.Platform.ToString()}"); | Console.WriteLine($"Environment.OSVersion.Platform:{Environment.OSVersion.Platform.ToString()}"); | ||||
NLog.LogManager.LoadConfiguration($"Configs/nlog.{Environment.OSVersion.Platform.ToString()}.config"); | NLog.LogManager.LoadConfiguration($"Configs/nlog.{Environment.OSVersion.Platform.ToString()}.config"); | ||||
logging.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true }); | logging.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true }); | ||||
@@ -26,8 +26,7 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
private MessageDispatchDataService messageDispatchDataService; | private MessageDispatchDataService messageDispatchDataService; | ||||
private ConcurrentDictionary<string, FMp4AVContext> avFrameDict; | private ConcurrentDictionary<string, FMp4AVContext> avFrameDict; | ||||
private H264Decoder H264Decoder; | private H264Decoder H264Decoder; | ||||
List<NalUnitType> NaluFilter; | |||||
BlockingCollection<(string SIM, byte ChannelNo,byte[]FirstBuffer, byte[] OtherBuffer)> FMp4Blocking; | |||||
BlockingCollection<(string SIM, byte ChannelNo,byte[]FirstBuffer, byte[] AVFrameInfoBuffer, byte[] OtherBuffer)> FMp4Blocking; | |||||
public JT1078FMp4NormalMsgHostedService( | public JT1078FMp4NormalMsgHostedService( | ||||
MessageDispatchDataService messageDispatchDataService, | MessageDispatchDataService messageDispatchDataService, | ||||
ILoggerFactory loggerFactory, | ILoggerFactory loggerFactory, | ||||
@@ -41,12 +40,7 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
H264Decoder= h264Decoder; | H264Decoder= h264Decoder; | ||||
this.messageDispatchDataService = messageDispatchDataService; | this.messageDispatchDataService = messageDispatchDataService; | ||||
avFrameDict = new ConcurrentDictionary<string, FMp4AVContext>(); | avFrameDict = new ConcurrentDictionary<string, FMp4AVContext>(); | ||||
FMp4Blocking=new BlockingCollection<(string SIM, byte ChannelNo, byte[] FirstBuffer, byte[] OtherBuffer)>(); | |||||
NaluFilter = new List<NalUnitType>(); | |||||
NaluFilter.Add(NalUnitType.SEI); | |||||
NaluFilter.Add(NalUnitType.PPS); | |||||
NaluFilter.Add(NalUnitType.SPS); | |||||
NaluFilter.Add(NalUnitType.AUD); | |||||
FMp4Blocking=new BlockingCollection<(string SIM, byte ChannelNo, byte[] FirstBuffer, byte[] AVFrameInfoBuffer, byte[] OtherBuffer)>(); | |||||
} | } | ||||
public Task StartAsync(CancellationToken cancellationToken) | public Task StartAsync(CancellationToken cancellationToken) | ||||
@@ -57,74 +51,32 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
var data = await messageDispatchDataService.FMp4Channel.Reader.ReadAsync(); | var data = await messageDispatchDataService.FMp4Channel.Reader.ReadAsync(); | ||||
try | try | ||||
{ | { | ||||
if (Logger.IsEnabled(LogLevel.Debug)) | |||||
//if (Logger.IsEnabled(LogLevel.Debug)) | |||||
//{ | |||||
// Logger.LogDebug(JsonSerializer.Serialize(HttpSessionManager.GetAll())); | |||||
// Logger.LogDebug($"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); | |||||
//} | |||||
bool keyframe = data.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧; | |||||
JT1078AVFrame avframe = H264Decoder.ParseAVFrame(data); | |||||
if (avframe.Nalus!= null && avframe.Nalus.Count>0) | |||||
{ | { | ||||
Logger.LogDebug(JsonSerializer.Serialize(HttpSessionManager.GetAll())); | |||||
Logger.LogDebug($"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); | |||||
} | |||||
List<H264NALU> h264NALUs = H264Decoder.ParseNALU(data); | |||||
if (h264NALUs!=null && h264NALUs.Count>0) | |||||
{ | |||||
if(!avFrameDict.TryGetValue(data.GetKey(),out FMp4AVContext cacheFrame)) | |||||
if(!avFrameDict.TryGetValue(data.GetAVKey(),out FMp4AVContext cacheFrame)) | |||||
{ | { | ||||
cacheFrame=new FMp4AVContext(); | cacheFrame=new FMp4AVContext(); | ||||
avFrameDict.TryAdd(data.GetKey(), cacheFrame); | |||||
avFrameDict.TryAdd(data.GetAVKey(), cacheFrame); | |||||
} | } | ||||
foreach(var nalu in h264NALUs) | |||||
if(keyframe) | |||||
{ | { | ||||
if (NaluFilter.Contains(nalu.NALUHeader.NalUnitType)) | |||||
if(avframe.SPS!=null && avframe.PPS != null) | |||||
{ | { | ||||
if (nalu.NALUHeader.NalUnitType== NalUnitType.SPS) | |||||
{ | |||||
cacheFrame.SPSNalu=nalu; | |||||
} | |||||
else if (nalu.NALUHeader.NalUnitType== NalUnitType.PPS) | |||||
{ | |||||
cacheFrame.PPSNalu=nalu; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
cacheFrame.NALUs.Add(nalu); | |||||
cacheFrame.AVFrameInfoBuffer = JsonSerializer.SerializeToUtf8Bytes( | |||||
new { Codecs = avframe .ToCodecs(), Width = avframe .Width, Height =avframe.Height}); | |||||
cacheFrame.FirstCacheBuffer = FM4Encoder.FirstVideoBox(avframe); | |||||
} | } | ||||
} | } | ||||
if (cacheFrame.NALUs.Count>1) | |||||
if (cacheFrame.FirstCacheBuffer != null) | |||||
{ | { | ||||
if (cacheFrame.FirstCacheBuffer==null) | |||||
{ | |||||
cacheFrame.FirstCacheBuffer=FM4Encoder.FirstVideoBox(cacheFrame.SPSNalu, cacheFrame.PPSNalu); | |||||
} | |||||
List<H264NALU> tmp = new List<H264NALU>(); | |||||
int i = 0; | |||||
foreach (var item in cacheFrame.NALUs) | |||||
{ | |||||
if (item.NALUHeader.KeyFrame) | |||||
{ | |||||
if (tmp.Count>0) | |||||
{ | |||||
FMp4Blocking.Add((data.SIM, data.LogicChannelNumber, cacheFrame.FirstCacheBuffer, FM4Encoder.OtherVideoBox(tmp))); | |||||
i+=tmp.Count; | |||||
tmp.Clear(); | |||||
} | |||||
tmp.Add(item); | |||||
i+=tmp.Count; | |||||
FMp4Blocking.Add((data.SIM, data.LogicChannelNumber, cacheFrame.FirstCacheBuffer, FM4Encoder.OtherVideoBox(tmp))); | |||||
tmp.Clear(); | |||||
cacheFrame.PrevPrimaryNalu = item; | |||||
continue; | |||||
} | |||||
if (cacheFrame.PrevPrimaryNalu!=null) //第一帧I帧 | |||||
{ | |||||
if (tmp.Count>1) | |||||
{ | |||||
FMp4Blocking.Add((data.SIM, data.LogicChannelNumber, cacheFrame.FirstCacheBuffer, FM4Encoder.OtherVideoBox(tmp))); | |||||
i+=tmp.Count; | |||||
tmp.Clear(); | |||||
} | |||||
tmp.Add(item); | |||||
} | |||||
} | |||||
cacheFrame.NALUs.RemoveRange(0, i); | |||||
FMp4Blocking.Add((data.SIM, data.LogicChannelNumber, cacheFrame.FirstCacheBuffer, cacheFrame.AVFrameInfoBuffer,FM4Encoder.OtherVideoBox(avframe.Nalus, data.GetAVKey(), keyframe))); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -132,13 +84,18 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
{ | { | ||||
Logger.LogError(ex, $"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); | Logger.LogError(ex, $"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); | ||||
} | } | ||||
} | } | ||||
}); | }); | ||||
Task.Run(() => { | Task.Run(() => { | ||||
//var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "JT1078_7_4_4.mp4"); | |||||
//if (File.Exists(filepath)) | |||||
//{ | |||||
// File.Delete(filepath); | |||||
//} | |||||
//using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||||
try | try | ||||
{ | { | ||||
foreach(var item in FMp4Blocking.GetConsumingEnumerable(cancellationToken)) | |||||
foreach (var item in FMp4Blocking.GetConsumingEnumerable(cancellationToken)) | |||||
{ | { | ||||
var httpSessions = HttpSessionManager.GetAllBySimAndChannelNo(item.SIM.TrimStart('0'), item.ChannelNo); | var httpSessions = HttpSessionManager.GetAllBySimAndChannelNo(item.SIM.TrimStart('0'), item.ChannelNo); | ||||
var firstHttpSessions = httpSessions.Where(w => !w.FirstSend && (w.RTPVideoType == Metadata.RTPVideoType.Http_FMp4 || w.RTPVideoType == Metadata.RTPVideoType.Ws_FMp4)).ToList(); | var firstHttpSessions = httpSessions.Where(w => !w.FirstSend && (w.RTPVideoType == Metadata.RTPVideoType.Http_FMp4 || w.RTPVideoType == Metadata.RTPVideoType.Ws_FMp4)).ToList(); | ||||
@@ -148,8 +105,11 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
//首帧 | //首帧 | ||||
foreach (var session in firstHttpSessions) | foreach (var session in firstHttpSessions) | ||||
{ | { | ||||
HttpSessionManager.SendAVData(session, item.FirstBuffer, true); | |||||
HttpSessionManager.SendAVData(session, item.AVFrameInfoBuffer, true); | |||||
HttpSessionManager.SendAVData(session, item.FirstBuffer, false); | |||||
//fileStream.Write(item.FirstBuffer); | |||||
HttpSessionManager.SendAVData(session, item.OtherBuffer, false); | HttpSessionManager.SendAVData(session, item.OtherBuffer, false); | ||||
//fileStream.Write(item.OtherBuffer); | |||||
} | } | ||||
} | } | ||||
if (otherHttpSessions.Count > 0) | if (otherHttpSessions.Count > 0) | ||||
@@ -158,6 +118,7 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
foreach (var session in otherHttpSessions) | foreach (var session in otherHttpSessions) | ||||
{ | { | ||||
HttpSessionManager.SendAVData(session, item.OtherBuffer, false); | HttpSessionManager.SendAVData(session, item.OtherBuffer, false); | ||||
//fileStream.Write(item.OtherBuffer); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -166,6 +127,7 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
{ | { | ||||
} | } | ||||
//fileStream.Close(); | |||||
}); | }); | ||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
@@ -177,11 +139,8 @@ namespace JT1078.Gateway.TestNormalHosting.Services | |||||
public class FMp4AVContext | public class FMp4AVContext | ||||
{ | { | ||||
public byte[] AVFrameInfoBuffer { get; set; } | |||||
public byte[] FirstCacheBuffer { get; set; } | public byte[] FirstCacheBuffer { get; set; } | ||||
public H264NALU PrevPrimaryNalu { get; set; } | |||||
public H264NALU SPSNalu { get; set; } | |||||
public H264NALU PPSNalu { get; set; } | |||||
public List<H264NALU> NALUs { get; set; } = new List<H264NALU>(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,14 +1,13 @@ | |||||
{ | { | ||||
"Logging": { | "Logging": { | ||||
"IncludeScopes": false, | |||||
"Debug": { | "Debug": { | ||||
"LogLevel": { | "LogLevel": { | ||||
"Default": "Trace" | |||||
"Default": "Information" | |||||
} | } | ||||
}, | }, | ||||
"Console": { | "Console": { | ||||
"LogLevel": { | "LogLevel": { | ||||
"Default": "Trace" | |||||
"Default": "Information" | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -7,198 +7,116 @@ | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<h1>MSE FMp4 Demo</h1> | <h1>MSE FMp4 Demo</h1> | ||||
<video id="stream_live" width="640" height="480" controls="false" autoplay="true" | |||||
muted="muted" | |||||
preload="auto"> | |||||
<video id="stream_live" autoplay> | |||||
浏览器不支持 | 浏览器不支持 | ||||
</video> | </video> | ||||
<ul id="messagesList"></ul> | |||||
<script> | <script> | ||||
//var mimeCodec = 'video/mp4;codecs="avc1.4D0014, mp4a.40.2"'; | |||||
// *** USER PARAMETERS *** | |||||
var verbose = true; | |||||
// var verbose = true; // enable for saturating the console .. | |||||
var buffering_sec = 1; // use some reasonable value | |||||
var buffering_sec_seek = buffering_sec * 0.9; | |||||
// ..seek the stream if it's this much away or | |||||
// from the last available timestamp | |||||
var buffering_sec_seek_distance = buffering_sec * 0.5; | |||||
// .. jump to this distance from the last avail. timestamp | |||||
// *** INTERNAL PARAMETERS *** | |||||
// set mimetype and codec | |||||
var mimeType = "video/mp4"; | |||||
var codecs = "avc1.4D0014"; // https://wiki.whatwg.org/wiki/Video_type_parameters | |||||
// if your stream has audio, remember to include it in these definitions.. otherwise your mse goes sour | |||||
var codecPars = mimeType + ';codecs="' + codecs + '"'; | |||||
var stream_started = false; // is the source_buffer updateend callback active nor not | |||||
// create media source instance | |||||
var ms = new MediaSource(); | |||||
// queue for incoming media packets | |||||
var queue = []; | |||||
var stream_live; // the HTMLMediaElement (i.e. <video> element) | |||||
var ws; // websocket | |||||
var seeked = false; // have have seeked manually once .. | |||||
var cc = 0; | |||||
var source_buffer; // source_buffer instance | |||||
var pass = 0; | |||||
// *** MP4 Box manipulation functions *** | |||||
// taken from here: https://stackoverflow.com/questions/54186634/sending-periodic-metadata-in-fragmented-live-mp4-stream/ | |||||
function toInt(arr, index) { // From bytes to big-endian 32-bit integer. Input: Uint8Array, index | |||||
var dv = new DataView(arr.buffer, 0); | |||||
return dv.getInt32(index, false); // big endian | |||||
/* ref https://github.com/v354412101/wsPlayer.git */ | |||||
function JT1078Player(videoId, wsUrl) { | |||||
this.videoId = videoId; | |||||
this.wsUrl = wsUrl; | |||||
this.ws = null; | |||||
this.verbose = true; | |||||
this.frameQueue = []; | |||||
} | } | ||||
function toString(arr, fr, to) { // From bytes to string. Input: Uint8Array, start index, stop index. | |||||
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String | |||||
return String.fromCharCode.apply(null, arr.slice(fr, to)); | |||||
} | |||||
function getBox(arr, i) { // input Uint8Array, start index | |||||
return [toInt(arr, i), toString(arr, i + 4, i + 8)] | |||||
} | |||||
function getSubBox(arr, box_name) { // input Uint8Array, box name | |||||
var i = 0; | |||||
res = getBox(arr, i); | |||||
main_length = res[0]; name = res[1]; // this boxes length and name | |||||
i = i + 8; | |||||
var sub_box = null; | |||||
while (i < main_length) { | |||||
res = getBox(arr, i); | |||||
l = res[0]; name = res[1]; | |||||
if (box_name == name) { | |||||
sub_box = arr.slice(i, i + l) | |||||
JT1078Player.prototype.open = function () { | |||||
let sourcebuffer = null; | |||||
this.ws = new WebSocket(this.wsUrl); | |||||
this.ws.binaryType = 'arraybuffer'; | |||||
let firstMessage = true; | |||||
let initMediaSource = function (avframe) { | |||||
let video = document.getElementById(this.videoId); | |||||
let mediasource = new MediaSource(); | |||||
video.src = URL.createObjectURL(mediasource); | |||||
let pre_pos = 0; | |||||
mediasource.onsourceopen = function () { | |||||
sourcebuffer = mediasource.addSourceBuffer('video/mp4; codecs=' + avframe.Codecs); | |||||
sourcebuffer.onupdateend = function () { | |||||
let pos = video.currentTime; | |||||
if (video.buffered.length > 0) { | |||||
let start = video.buffered.start(video.buffered.length - 1); | |||||
let end = video.buffered.end(video.buffered.length - 1); | |||||
if (this.verbose) | |||||
console.log("pos=" + pos + ",start=" + start + ",end=" + end); | |||||
if (pos < start) { | |||||
if (this.verbose) | |||||
console.log("set video.currentTime pos=" + pos + ",start=" + start + ",end=" + end); | |||||
video.currentTime = start; | |||||
} | |||||
if (pos > end) { | |||||
if (this.verbose) | |||||
console.warn("chase frame pos=" + pos + ",start=" + start + ",end=" + end); | |||||
video.currentTime = start; | |||||
} | |||||
if (pos - pre_pos != 0 && end - pos > 3) { | |||||
if (this.verbose) | |||||
console.log("set end video.currentTime pos=" + pos + ",start=" + start + ",end=" + end); | |||||
video.currentTime = end; | |||||
} | |||||
for (let i = 0; i < video.buffered.length - 1; i++) { | |||||
let prestart = video.buffered.start(i); | |||||
let preend = video.buffered.end(i); | |||||
if (!sourcebuffer.updating) { | |||||
sourcebuffer.remove(prestart, preend); | |||||
} | |||||
} | |||||
if (pos - start > 10 && !sourcebuffer.updating) { | |||||
if (this.verbose) | |||||
console.warn("remove start pos=" + pos + ",start=" + start + ",end=" + end); | |||||
sourcebuffer.remove(0, pos - 3); | |||||
} | |||||
if (end - pos > 10 && !sourcebuffer.updating) { | |||||
if (this.verbose) | |||||
console.warn("remove end pos=" + pos + ",start=" + start + ",end=" + end); | |||||
sourcebuffer.remove(0, end - 3); | |||||
} | |||||
} | |||||
pre_pos = pos; | |||||
} | |||||
} | } | ||||
i = i + l; | |||||
} | |||||
return sub_box; | |||||
} | |||||
function hasFirstSampleFlag(arr) { // input Uint8Array | |||||
// [moof [mfhd] [traf [tfhd] [tfdt] [trun]]] | |||||
var traf = getSubBox(arr, "traf"); | |||||
if (traf == null) { return false; } | |||||
var trun = getSubBox(traf, "trun"); | |||||
if (trun == null) { return false; } | |||||
// ISO/IEC 14496-12:2012(E) .. pages 5 and 57 | |||||
// bytes: (size 4), (name 4), (version 1 + tr_flags 3) | |||||
var flags = trun.slice(10, 13); // console.log(flags); | |||||
f = flags[1] & 4; // console.log(f); | |||||
return f == 4; | |||||
} | |||||
// consider these callbacks: | |||||
// - putPacket : called when websocket receives data | |||||
// - loadPacket : called when source_buffer is ready for more data | |||||
// Both operate on a common fifo | |||||
function putPacket(arr) { | |||||
// receives ArrayBuffer. Called when websocket gets more data | |||||
// first packet ever to arrive: write directly to source_buffer | |||||
// source_buffer ready to accept: write directly to source_buffer | |||||
// otherwise insert it to queue | |||||
var memview = new Uint8Array(arr); | |||||
if (verbose) { console.log("got", arr.byteLength, "bytes. Values=", memview[0], memview[1], memview[2], memview[3], memview[4]); } | |||||
res = getBox(memview, 0); | |||||
main_length = res[0]; name = res[1]; // this boxes length and name | |||||
// if ((name == "ftyp") && (pass == 0)) { | |||||
// pass = pass + 1; | |||||
// console.log("got ftyp"); | |||||
// } | |||||
// else if ((name == "moov") && (pass == 1)) { | |||||
// pass = pass + 1; | |||||
// console.log("got moov"); | |||||
// } | |||||
// else if ((name == "moof") && (pass == 2)) { | |||||
// if (hasFirstSampleFlag(memview)) { | |||||
// pass = pass + 1; | |||||
// console.log("got that special moof"); | |||||
// } | |||||
// else { | |||||
// return; | |||||
// } | |||||
// } | |||||
// else if (pass < 3) { | |||||
// return; | |||||
// } | |||||
// keep the latency to minimum | |||||
// let latest = stream_live.duration; | |||||
// if ((stream_live.duration >= buffering_sec) && ((latest - stream_live.currentTime) > buffering_sec_seek)) { | |||||
// console.log("seek from ", stream_live.currentTime, " to ", latest); | |||||
// df = (stream_live.duration - stream_live.currentTime); // this much away from the last available frame | |||||
// if ((df > buffering_sec_seek)) { | |||||
// seek_to = stream_live.duration - buffering_sec_seek_distance; | |||||
// stream_live.currentTime = seek_to; | |||||
// } | |||||
// } | |||||
if (!source_buffer.updating) { | |||||
if (verbose) { console.log("Streaming started: ", memview[0], memview[1], memview[2], memview[3], memview[4]); } | |||||
stream_started = true; | |||||
source_buffer.appendBuffer(arr); | |||||
cc = cc + 1; | |||||
}else{ | |||||
queue.push(arr); // add to the end | |||||
} | |||||
if (verbose) { console.log("queue push:", queue.length); } | |||||
} | |||||
}.bind(this); | |||||
function loadPacket() { // called when source_buffer is ready for more | |||||
if (!source_buffer.updating) { // really, really ready | |||||
if (queue.length > 0) { | |||||
var inp = queue.shift(); // pop from the beginning | |||||
if (verbose) { console.log("queue pop:", queue.length); } | |||||
var memview = new Uint8Array(inp); | |||||
if (verbose) { console.log(" ==> writing buffer with", memview[0], memview[1], memview[2], memview[3]); } | |||||
source_buffer.appendBuffer(inp); | |||||
cc = cc + 1; | |||||
this.ws.onmessage = function (e) { | |||||
//首帧为音视频信息 | |||||
if (firstMessage) { | |||||
firstMessage = false; | |||||
let avframe = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(e.data))); | |||||
if(this.verbose) | |||||
console.log("avframe", avframe); | |||||
initMediaSource(avframe); | |||||
return; | |||||
} | } | ||||
// else { // the queue runs empty, so the next packet is fed directly | |||||
// stream_started = false; | |||||
// } | |||||
} | |||||
// else { | |||||
// // so it was not? | |||||
// } | |||||
} | |||||
function opened() { // MediaSource object is ready to go | |||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/duration | |||||
ms.duration = buffering_sec; | |||||
source_buffer = ms.addSourceBuffer(codecPars); | |||||
// https://developer.mozilla.org/en-US/docs/Web/API/source_buffer/mode | |||||
var myMode = source_buffer.mode; | |||||
source_buffer.mode = 'sequence'; | |||||
// source_buffer.mode = 'segments'; | |||||
source_buffer.addEventListener("updateend", loadPacket); | |||||
ws = new WebSocket("ws://49.235.89.102:15555/live.mp4?sim=40281815788&channel=1&token=123456"); | |||||
//ws = new WebSocket("ws://49.235.89.102:15555/live.mp4?sim=1901305037&channel=2&token=123456"); | |||||
//ws = new WebSocket("ws://127.0.0.1:81/live/JT1078_8.live.mp4"); //创建WebSocket连接 | |||||
ws.binaryType = 'arraybuffer'; | |||||
ws.onmessage = function (e) { | |||||
//当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据 | |||||
//console.log(e.data); | |||||
putPacket(e.data); | |||||
//source_buffer.appendBuffer(e.data); | |||||
} | |||||
} | |||||
function startup() { | |||||
ms.addEventListener('sourceopen', opened, false); | |||||
// get reference to video | |||||
stream_live = document.getElementById('stream_live'); | |||||
// set mediasource as source of video | |||||
stream_live.src = window.URL.createObjectURL(ms); | |||||
} | |||||
function base64ToArrayBuffer(base64) { | |||||
var binary_string = window.atob(base64); | |||||
var len = binary_string.length; | |||||
var bytes = new Uint8Array(len); | |||||
for (var i = 0; i < len; i++) { | |||||
bytes[i] = binary_string.charCodeAt(i); | |||||
} | |||||
return bytes.buffer; | |||||
this.frameQueue.push(e.data); | |||||
if (!sourcebuffer || sourcebuffer.updating) { | |||||
return; | |||||
} | |||||
if (this.frameQueue.length === 1) { | |||||
sourcebuffer.appendBuffer(this.frameQueue.shift()); | |||||
} else { | |||||
let byte_length = 0; | |||||
for (const qnode of this.frameQueue) { | |||||
byte_length += qnode.byteLength; | |||||
} | |||||
let mp4buf = new Uint8Array(byte_length); | |||||
let offset = 0; | |||||
for (const qnode of this.frameQueue) { | |||||
let frame = new Uint8Array(qnode); | |||||
mp4buf.set(frame, offset); | |||||
offset += qnode.byteLength; | |||||
} | |||||
sourcebuffer.appendBuffer(mp4buf); | |||||
this.frameQueue.splice(0, this.frameQueue.length); | |||||
} | |||||
}.bind(this); | |||||
} | } | ||||
window.onload = function () { | |||||
startup(); | |||||
JT1078Player.prototype.close = function () { | |||||
this.ws && this.ws.close(); | |||||
} | } | ||||
document.addEventListener('DOMContentLoaded', function () { | |||||
var player = new JT1078Player("stream_live", "ws://127.0.0.1:15555/live.mp4?sim=11111111111&channel=1&token=123456"); | |||||
player.open(); | |||||
}); | |||||
</script> | </script> | ||||
</body> | </body> | ||||
</html> | </html> |
@@ -20,8 +20,11 @@ EndProject | |||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6631FE91-3525-4A84-937B-781C73B343C9}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6631FE91-3525-4A84-937B-781C73B343C9}" | ||||
ProjectSection(SolutionItems) = preProject | ProjectSection(SolutionItems) = preProject | ||||
Info.props = Info.props | Info.props = Info.props | ||||
..\README.md = ..\README.md | |||||
EndProjectSection | EndProjectSection | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.FMp4", "..\..\JT1078\src\JT1078.FMp4\JT1078.FMp4.csproj", "{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -52,6 +55,10 @@ Global | |||||
{011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.Build.0 = Release|Any CPU | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -27,9 +27,9 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="JT1078.Hls" Version="1.2.0-preview2" /> | |||||
<PackageReference Include="JT1078.FMp4" Version="1.2.0-preview2" /> | |||||
<PackageReference Include="JT1078.Flv" Version="1.2.0-preview2" /> | |||||
<PackageReference Include="JT1078.FMp4" Version="1.2.0-preview6" /> | |||||
<PackageReference Include="JT1078.Hls" Version="1.2.0-preview6" /> | |||||
<PackageReference Include="JT1078.Flv" Version="1.2.0-preview6" /> | |||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" /> | <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" /> | ||||
<PackageReference Include="System.IO.Pipelines" Version="6.0.1" /> | <PackageReference Include="System.IO.Pipelines" Version="6.0.1" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -40,6 +40,7 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<!--<ProjectReference Include="..\..\..\JT1078\src\JT1078.FMp4\JT1078.FMp4.csproj" />--> | |||||
<ProjectReference Include="..\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj" /> | <ProjectReference Include="..\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |