asp.net-core-mvc – 如何考虑请求和响应范围标头流式传输视频或文件?

我现在正在使用FileStreamResult,它可以传输视频,但无法搜索它.它总是从头开始.

我正在使用ByteRangeStreamContent,但它似乎不再可用于dnxcore50.

那怎么办?

我是否需要手动解析请求范围标头并编写自定义FileResult来设置响应Content-Range和其余标头,并将缓冲区范围写入响应主体,或者是否已经实现了某些内容并且我错过了它?

解决方法

这是一个VideoStreamResult的简单实现,我现在正在使用(多部分内容部分未经过测试):
public class VideoStreamResult : FileStreamResult
{
    // default buffer size as defined in BufferedStream type
    private const int BufferSize = 0x1000;
    private string MultipartBoundary = "<qwe123>";

    public VideoStreamResult(Stream fileStream,string contentType)
        : base(fileStream,contentType)
    {

    }

    public VideoStreamResult(Stream fileStream,MediaTypeHeaderValue contentType) 
        : base(fileStream,contentType)
    {

    }

    private bool IsMultipartRequest(RangeHeaderValue range)
    {
        return range != null && range.Ranges != null && range.Ranges.Count > 1;
    }

    private bool IsRangeRequest(RangeHeaderValue range)
    {
        return range != null && range.Ranges != null && range.Ranges.Count > 0;
    }

    protected async Task WriteVideoAsync(HttpResponse response)
    {
        var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
        bufferingFeature?.DisableResponseBuffering();

        var length = FileStream.Length;

        var range = response.HttpContext.GetRanges(length);

        if (IsMultipartRequest(range))
        {
            response.ContentType = $"multipart/byteranges; boundary={MultipartBoundary}";
        }
        else
        {
            response.ContentType = ContentType.ToString();
        }

        response.Headers.Add("Accept-Ranges","bytes");

        if (IsRangeRequest(range))
        {
            response.StatusCode = (int)HttpStatusCode.PartialContent;

            if (!IsMultipartRequest(range))
            {
                response.Headers.Add("Content-Range",$"bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
            }

            foreach (var rangeValue in range.Ranges)
            {
                if (IsMultipartRequest(range)) // dunno if multipart works
                {
                    await response.WriteAsync($"--{MultipartBoundary}");
                    await response.WriteAsync(Environment.NewLine);
                    await response.WriteAsync($"Content-type: {ContentType}");
                    await response.WriteAsync(Environment.NewLine);
                    await response.WriteAsync($"Content-Range: bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
                    await response.WriteAsync(Environment.NewLine);
                }

                await WriteDataToResponseBody(rangeValue,response);

                if (IsMultipartRequest(range))
                {
                    await response.WriteAsync(Environment.NewLine);
                }
            }

            if (IsMultipartRequest(range))
            {
                await response.WriteAsync($"--{MultipartBoundary}--");
                await response.WriteAsync(Environment.NewLine);
            }
        }
        else
        {
            await FileStream.CopyToAsync(response.Body);
        }
    }

    private async Task WriteDataToResponseBody(RangeItemHeaderValue rangeValue,HttpResponse response)
    {
        var startIndex = rangeValue.From ?? 0;
        var endIndex = rangeValue.To ?? 0;

        byte[] buffer = new byte[BufferSize];
        long totalToSend = endIndex - startIndex;
        int count = 0;

        long bytesRemaining = totalToSend + 1;
        response.ContentLength = bytesRemaining;

        FileStream.Seek(startIndex,SeekOrigin.Begin);

        while (bytesRemaining > 0)
        {
            try
            {
                if (bytesRemaining <= buffer.Length)
                    count = FileStream.Read(buffer,(int)bytesRemaining);
                else
                    count = FileStream.Read(buffer,buffer.Length);

                if (count == 0)
                    return;

                await response.Body.WriteAsync(buffer,count);

                bytesRemaining -= count;
            }
            catch (IndexOutOfRangeException)
            {
                await response.Body.FlushAsync();
                return;
            }
            finally
            {
                await response.Body.FlushAsync();
            }
        }
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        await WriteVideoAsync(context.HttpContext.Response);
    }
}

并解析请求标头范围:

public static RangeHeaderValue GetRanges(this HttpContext context,long contentSize)
        {
            RangeHeaderValue rangesResult = null;

            string rangeHeader = context.Request.Headers["Range"];

            if (!string.IsNullOrEmpty(rangeHeader))
            {
                // rangeHeader contains the value of the Range HTTP Header and can have values like:
                //      Range: bytes=0-1            * Get bytes 0 and 1,inclusive
                //      Range: bytes=0-500          * Get bytes 0 to 500 (the first 501 bytes),inclusive
                //      Range: bytes=400-1000       * Get bytes 500 to 1000 (501 bytes in total),inclusive
                //      Range: bytes=-200           * Get the last 200 bytes
                //      Range: bytes=500-           * Get all bytes from byte 500 to the end
                //
                // Can also have multiple ranges delimited by commas,as in:
                //      Range: bytes=0-500,600-1000 * Get bytes 0-500 (the first 501 bytes),inclusive plus bytes 600-1000 (401 bytes) inclusive

                // Remove "Ranges" and break up the ranges
                string[] ranges = rangeHeader.Replace("bytes=",string.Empty).Split(",".tocharArray());

                rangesResult = new RangeHeaderValue();

                for (int i = 0; i < ranges.Length; i++)
                {
                    const int START = 0,END = 1;

                    long endByte,startByte;

                    long parsedValue;

                    string[] currentRange = ranges[i].Split("-".tocharArray());

                    if (long.TryParse(currentRange[END],out parsedValue))
                        endByte = parsedValue;
                    else
                        endByte = contentSize - 1;


                    if (long.TryParse(currentRange[START],out parsedValue))
                        startByte = parsedValue;
                    else
                    {
                        // No beginning specified,get last n bytes of file
                        // We already parsed end,so subtract from total and
                        // make end the actual size of the file
                        startByte = contentSize - endByte;
                        endByte = contentSize - 1;
                    }

                    rangesResult.Ranges.Add(new RangeItemHeaderValue(startByte,endByte));
                }
            }

            return rangesResult;
        }

相关文章

项目要求通过网站上传大文件,比如视频文件,通过摸索实现了文件分片来上传,然后后台进行合并。 使用了...
安装新版本的Nginx(vim /etc/yum.repos.d/nginx.repo) [nginx-stable] name=nginx stable repo baseu...
什么是 SignalR&#160;ASP.NET Core ASP.NET Core SignalR 是一种开放源代码库,可简化将实时 web 功...
在Windows下使用Docker,我们选择Docker Desktop这个软件,非常方便。 ## Docker Desktop介绍及安装 Do...
项目开始设计的是运行在windows下,所以一开始采用的是windows服务模式来获取多媒体文件信息,后来要求...
银河麒麟高级服务器操作系统V10是针对企业级关键业务,适应虚拟化、云计算、大数据、工业互联网时代对主...