Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Video streaming in .net core (using status code 206)

My .NET Core application will streaming video from storage. I've found this solution https://www.codeproject.com/Articles/820146/HTTP-Partial-Content-In-ASP-NET-Web-API-Video

And try to rewrite it like this

    [AllowAnonymous]
    [HttpGet("{container}/{name}")]
    public IActionResult Get(string container, string name)
    {
        var stream = _fileStorageClient.GetStream(container, name); //Got from storage
        if (stream == null)
            return NotFound();

        Response.Headers["Accept-Ranges"] = "bytes";

        //if there is no range - this is usual request
        var rangeHeaderValue = Request.Headers["Range"].FirstOrDefault();
        if (string.IsNullOrEmpty(rangeHeaderValue))
        {
            var fileStreamResult = new FileStreamResult(stream, "video/mp4");
            Response.ContentLength = stream.Length;
            Response.StatusCode = (int) HttpStatusCode.OK;
            return fileStreamResult;
        }

        if (!TryReadRangeItem(rangeHeaderValue, stream.Length, out long start, out long end))
        {
            return StatusCode((int) HttpStatusCode.RequestedRangeNotSatisfiable);
        }

        Response.Headers["Content-Range"] = $"{start}-{end}/{stream.Length}";
        Response.ContentLength = end - start + 1;
        Response.StatusCode = (int) HttpStatusCode.PartialContent;

        var outStream = new MemoryStream();
        CreatePartialContent(stream, outStream, start, end);
        outStream.Seek(0, SeekOrigin.Begin);
        return new FileStreamResult(outStream, "video/mp4");
    }

    private static void CreatePartialContent(Stream inputStream, Stream outputStream,
        long start, long end)
    {
        var remainingBytes = end - start + 1;
        var buffer = new byte[ReadStreamBufferSize];
        long position;
        inputStream.Position = start;
        do
        {
            try
            {
                var count = remainingBytes > ReadStreamBufferSize ? 
                    inputStream.Read(buffer, 0, ReadStreamBufferSize) : 
                    inputStream.Read(buffer, 0, (int) remainingBytes);
                outputStream.Write(buffer, 0, count);
            }
            catch (Exception error)
            {
                Debug.WriteLine(error);
                break;
            }
            position = inputStream.Position;
            remainingBytes = end - position + 1;
        } while (position <= end);
    }

    private bool TryReadRangeItem(string rangeHeaderValue, long contentLength,
        out long start, out long end)
    {
        if (string.IsNullOrEmpty(rangeHeaderValue))
            throw new ArgumentNullException(nameof(rangeHeaderValue));

        start = 0;
        end = contentLength - 1;

        var rangeHeaderSplitted = rangeHeaderValue.Split('=');
        if (rangeHeaderSplitted.Length == 2)
        {
            var range = rangeHeaderSplitted[1].Split('-');
            if (range.Length == 2)
            {
                if (long.TryParse(range[0], out long startParsed))
                    start = startParsed;
                if (long.TryParse(range[1], out long endParsed))
                    end = endParsed;
            }
        }

        return start < contentLength && end < contentLength;
    }

When I try to execute action from home I've got two requests

Request 1

GET /api/videoFiles/video-answers/7f72c19b-5a5a-ed17-f87d-e755834a1c92 HTTP/1.1
Host: localhost:65086
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: vc=2; _ga=GA1.1.1273330415.1475670014

Response 1

HTTP/1.1 200 OK
Content-Length: 5517780
Content-Type: video/mp4
Accept-Ranges: bytes
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcV29ya1xIZXJ6ZW5cSGVyemVuLldlYlxhcGlcdmlkZW9GaWxlc1x2aWRlby1hbnN3ZXJzXDdmNzJjMTliLTVhNWEtZWQxNy1mODdkLWU3NTU4MzRhMWM5Mg==?=
X-Powered-By: ASP.NET
Date: Wed, 20 Sep 2017 06:37:01 GMT

Then Request 2

GET /api/videoFiles/video-answers/7f72c19b-5a5a-ed17-f87d-e755834a1c92 HTTP/1.1
Host: localhost:65086
Connection: keep-alive
Accept-Encoding: identity;q=1, *;q=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Accept: */*
Referer: http://localhost:65086/api/videoFiles/video-answers/7f72c19b-5a5a-ed17-f87d-e755834a1c92
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: vc=2; _ga=GA1.1.1273330415.1475670014
Range: bytes=0-

Response 2

HTTP/1.1 206 Partial Content
Content-Length: 5517780
Content-Type: video/mp4
Content-Range: 0-5517779/5517780
Accept-Ranges: bytes
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcV29ya1xIZXJ6ZW5cSGVyemVuLldlYlxhcGlcdmlkZW9GaWxlc1x2aWRlby1hbnN3ZXJzXDdmNzJjMTliLTVhNWEtZWQxNy1mODdkLWU3NTU4MzRhMWM5Mg==?=
X-Powered-By: ASP.NET
Date: Wed, 20 Sep 2017 06:37:01 GMT

After loading video control is available for instant, then it becomes grayed out

When I return FileStreamResult(as in first condition) without Accept-Ranges all is ok.

What's wrong with this code?

like image 582
takayoshi Avatar asked Oct 18 '22 05:10

takayoshi


1 Answers

In ASP.NET Core 2.1 was added support for Ranges: link.

Use File constructor with enableRangeProcessing:

FileStreamResult File(Stream fileStream, string contentType, bool enableRangeProcessing)

like image 164
Volmart Avatar answered Oct 21 '22 04:10

Volmart