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?
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With