Info: (HTTP/2) Kestrel hosted Blazor WASM ASP.NET Core 6 preview 1 / C# 9 Project.
I've written the code below to stream a video file in (actual physical) separate chunks from the server to the client, to prevent the average user to download the video in one click in (for example) the browser's DevTools.
It actually works (see the screenshot below the code snippets) and seeking works as well, but I'm wondering if I'm doing this right. I hope I can use SO for this, but I'm actually looking for help, advise and/or comments on my code. I've been working on this code block for the past 2 weeks, searching for code snippets, reading docs/blogs and now I finally have something that works but maybe I'm doing this wrong or maybe I'm forgetting something really important. Does this code scale is one of my questions. Am I using the using declarations correctly. Does the code looks okay anyways :)
FYI: I know about the enableRangeProcessing parameter, but AFAIK that isn't applicable in this case. And seeking does work.
/Server/Controllers/StreamController.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Threading.Tasks;
namespace Media.Server.Controllers
{
[Route("[controller]")]
public class StreamController : ControllerBase
{
private readonly IWebHostEnvironment env;
private readonly ILogger<StreamController> logger;
public StreamController(ILogger<StreamController> logger, IWebHostEnvironment env)
{
this.logger = logger;
this.env = env;
}
[HttpGet("{file}")]
public async Task GetStream(string file)
{
var provider = new PhysicalFileProvider(env.ContentRootPath);
string videoPathFile = Path.Combine(provider.Root, "Files", "Videos", $"{file}");
byte[] buffer = new byte[1024 * 1024 * 4]; // 'Chunks' of 4MB
long startPosition = 0;
if (!string.IsNullOrEmpty(Request.Headers["Range"]))
{
string[] range = Request.Headers["Range"].ToString().Split(new char[] { '=', '-' });
startPosition = long.Parse(range[1]);
}
using FileStream inputStream = new(videoPathFile, FileMode.Open, FileAccess.Read, FileShare.Read)
{
Position = startPosition
};
int chunkSize = await inputStream.ReadAsync(buffer.AsMemory(0, buffer.Length));
long fileSize = inputStream.Length;
if (chunkSize > 0)
{
Response.StatusCode = 206;
Response.Headers["Accept-Ranges"] = "bytes";
Response.Headers["Content-Range"] = string.Format($" bytes {startPosition}-{fileSize - 1}/{fileSize}");
Response.ContentType = "application/octet-stream";
using Stream outputStream = Response.Body;
await outputStream.WriteAsync(buffer.AsMemory(0, chunkSize));
};
}
}
}
(part of) client side .razor code
<video id="videostream" poster="@videoPoster" class="videostyle" oncontextmenu="return false;" controls disablePictureInPicture controlsList="nodownload">
<source src="/Stream/@videoFileName" type="@ContentType;codecs=@Codecs" />
Your browser does not support the video tag.
</video>
Screenshot of DevTools network pane
