0

我需要建议。我正在尝试通过 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 代码)

任何帮助将不胜感激,谢谢

4

0 回答 0