我需要建议。我正在尝试通过 EmbedIO 创建一个简单的 Web 服务器,以便通过简单地输入 URL: http: //192.168.0.165 : 9696/videos?seconds=0&file=D:\videos\video.mp4通过网络流式传输本地视频文件
服务器的代码如下:
static void StartServer(string[] args)
{
var url = "http://192.168.0.165:9696";
if (args.Length > 0)
url = args[0];
var server = new WebServer(o => o
.WithUrlPrefix(url)
.WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
.WithModule(new VideoModule("/videos"))
.WithModule(new ActionModule("/", HttpVerbs.Any, ctx =>
{
return ctx.SendDataAsync(new { Message = "Server initialized" });
}));
// Listen for state changes.
server.StateChanged += (s, e) => $"WebServer New State - {e.NewState}".Info();
server.Start();
var browser = new Process()
{
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
};
browser.Start();
Console.ReadKey(true);
}
VideoModule 的代码如下:
public class VideoModule : WebModuleBase
{
private readonly Process _transcodeProcess;
private int _seconds;
public VideoModule(string baseRoute) : base(baseRoute)
{
_transcodeProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = @"D:\ffmpeg\x64\ffmpeg.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
}
public override bool IsFinalHandler => false;
protected override Task OnRequestAsync(IHttpContext context)
{
var path = context.RequestedPath;
var verb = context.Request.HttpVerb;
var query = context.GetRequestQueryData();
var allowedQueryParametes = new[]
{
"file",
"seconds"
};
if (query.Count == 0 || !query.AllKeys.All(q => allowedQueryParametes.Contains(q)))
{
context.SetHandled();
return Task.CompletedTask;
}
try
{
string filepath = query["file"];
if (!File.Exists(filepath))
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
return Task.CompletedTask;
}
var duration = GetFileDuration(filepath);
var file = new FileInfo(filepath);
context.Response.Headers.Add("Accept-Ranges", "bytes");
context.Response.Headers.Add("Content-Duration", $"{Math.Round(duration, 2)}");
context.Response.ContentType = context.GetMimeType(file.Extension);
context.Response.DisableCaching();
if (context.Request.Headers.ContainsKey("Range"))
{
string[] range = context.Request.Headers["Range"].Split(new char[] { '=', '-' });
long start = long.Parse(range[1]);
if (start != 0)
{
_seconds += 60;
}
_transcodeProcess.StartInfo.Arguments = @$"-v quiet -ss {_seconds} -y -i ""{filepath}"" -t 60 -crf 28 -preset ultrafast -vcodec h264 -acodec aac -b:a 128k -movflags frag_keyframe+faststart -f mp4 -";
_transcodeProcess.Start();
_transcodeProcess.PriorityClass = ProcessPriorityClass.High;
var stream = _transcodeProcess.StandardOutput.BaseStream as FileStream;
var memStream = new MemoryStream();
stream.CopyTo(memStream);
_transcodeProcess.WaitForExit();
context.Response.StatusCode = 206;
context.Response.ContentLength64 = memStream.Length;
var responseRange = string.Format("bytes {0}-{1}/*", start, memStream.Length - 1);
context.Response.Headers.Add("Content-Range", responseRange);
memStream.Position = 0;
return memStream.CopyToAsync(context.Response.OutputStream);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
context.SetHandled();
}
return Task.CompletedTask;
}
private static double GetFileDuration(string filepath)
{
var p = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = @"D:\ffmpeg\x64\ffprobe.exe",
UseShellExecute = false,
LoadUserProfile = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false
}
};
p.StartInfo.Arguments = @$"-v quiet -i ""{filepath}"" -show_entries format=duration -of csv=p=0";
p.Start();
p.WaitForExit();
string stringDuration = p.StandardOutput.ReadToEnd();
return double.Parse(ReplaceNewlines(stringDuration, ""), System.Globalization.CultureInfo.InvariantCulture);
}
static string ReplaceNewlines(string blockOfText, string replaceWith)
{
return blockOfText.Replace("\r\n", replaceWith).Replace("\n", replaceWith).Replace("\r", replaceWith);
}
}
我使用ffmpeg对视频文件进行转码。这目前正在工作(至少在 Chrome 中),问题是我无法寻找视频,我认为这是因为我返回 200 状态代码(它的默认值)而不是 206 部分内容代码。
所以,我想,如果我想返回视频块,我需要向 ffmpeg 传递一些额外的参数,以便只对文件的一部分进行转码,所以我创建了一个变量 _seconds ,它将保持当前位置并且添加 -t 60 仅处理 60 秒
问题是它在视频的前 60 秒内有效,但随后 Chrome 视频播放器停止。出于某种原因,它没有要求下一个块,我不知道为什么(我认为这将自动处理,因为我返回了 206 代码)
任何帮助将不胜感激,谢谢