C
C#21h ago
Rushaan

Memory issue in ASP.NET Core video playback endpoint

[HttpGet("filePreview")]
public async Task<IActionResult> GetFileForPreview(string filePath)
{
if (!System.IO.File.Exists(filePath))
{
return NotFound("File not found");
}

var extension = Path.GetExtension(filePath).ToLowerInvariant();
var mimeType = extension switch
{
".txt" => "text/plain",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".mp4" => "video/mp4",
".webm" => "video/webm",
".mp3" => "audio/mpeg",
".wav" => "audio/wav",
".webp" => "image/webp",
".pdf" => "application/pdf",
_ => null
};

if (mimeType == null)
{
return BadRequest("Unsupported file type");
}
Response.Headers["X-Accel-Buffering"] = "no";
return PhysicalFile(filePath, mimeType, enableRangeProcessing: true);
}
[HttpGet("filePreview")]
public async Task<IActionResult> GetFileForPreview(string filePath)
{
if (!System.IO.File.Exists(filePath))
{
return NotFound("File not found");
}

var extension = Path.GetExtension(filePath).ToLowerInvariant();
var mimeType = extension switch
{
".txt" => "text/plain",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".mp4" => "video/mp4",
".webm" => "video/webm",
".mp3" => "audio/mpeg",
".wav" => "audio/wav",
".webp" => "image/webp",
".pdf" => "application/pdf",
_ => null
};

if (mimeType == null)
{
return BadRequest("Unsupported file type");
}
Response.Headers["X-Accel-Buffering"] = "no";
return PhysicalFile(filePath, mimeType, enableRangeProcessing: true);
}
<video preload="auto" *ngIf="file.fileType==FileType.Video" [src]="'https://localhost:7219/api/Retrievals/filePreview?filePath='+file.filePath" controls width="600"></video>
<video preload="auto" *ngIf="file.fileType==FileType.Video" [src]="'https://localhost:7219/api/Retrievals/filePreview?filePath='+file.filePath" controls width="600"></video>
I'm trying to implement a very basic endpoint which responds with file to angular front-end but when video is fetched its all good even when video is playing normally and even when I seek the video forward but the second I seek the video backwards and continuously seek it back and forth the ram usage increases by MBs the more I seek and since the time I seek the video back the ram usage keeps rising in 1 MBs or so every few seconds even as video is playing normally which didn't happen until i seek the video back. I'm talking about a 450 MB 3-4 mins video. The ram usage never goes down either, why's this happening, should I be concerned or will GC do its thing eventually or as memory rises more?
4 Replies
glhays
glhays21h ago
Maybe a filestream object will manage resources better than the buffering. https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream?view=net-9.0
FileStream Class (System.IO)
Provides a Stream for a file, supporting both synchronous and asynchronous read and write operations.
wasabi
wasabi21h ago
It would not. In fact it would likely make it worse.
Rushaan
RushaanOP21h ago
well I've tried this and it didn't really help, identical memory rising behaviour as in the above code
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 64 * 1024, useAsync: true);
Response.Headers["X-Accel-Buffering"] = "no";
return File(stream, mimeType, true);
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 64 * 1024, useAsync: true);
Response.Headers["X-Accel-Buffering"] = "no";
return File(stream, mimeType, true);
I also tried returning new FileStreamResult but same thing I also tried returning this by creating new class & implementing IActionResult with method below but literally got identical memory rising behavior as I described in the post:
public async Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
response.ContentType = _contentType;
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Connection"] = "close";

var poolBuf = ArrayPool<byte>.Shared.Rent(512 * 1024);

try
{
await using var fs = new FileStream(
_path, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: poolBuf.Length, useAsync: true);

long total = fs.Length;
var rangeHeader = context.HttpContext.Request.Headers["Range"].ToString();
var openEnded = rangeHeader.EndsWith("-", StringComparison.Ordinal);

var (start, intendedEnd) = ParseRange(context.HttpContext.Request, total);

var end = openEnded
? Math.Min(start + poolBuf.Length - 1, total - 1)
: intendedEnd;

var length = end - start + 1;
response.StatusCode = (start == 0 && end == total - 1) ? 200 : 206;
response.Headers["Content-Range"] = $"bytes {start}-{end}/{total}";
response.Headers["Content-Length"] = length.ToString();

fs.Seek(start, SeekOrigin.Begin);
var ct = context.HttpContext.RequestAborted;
long remaining = length;

while (remaining > 0 && !ct.IsCancellationRequested)
{
int toRead = (int)Math.Min(poolBuf.Length, remaining);
int read = await fs.ReadAsync(poolBuf, 0, toRead, ct);
if (read == 0) break;

await response.Body.WriteAsync(poolBuf, 0, read, ct);
await response.Body.FlushAsync(ct);
remaining -= read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(poolBuf);
}
}
public async Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
response.ContentType = _contentType;
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Connection"] = "close";

var poolBuf = ArrayPool<byte>.Shared.Rent(512 * 1024);

try
{
await using var fs = new FileStream(
_path, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: poolBuf.Length, useAsync: true);

long total = fs.Length;
var rangeHeader = context.HttpContext.Request.Headers["Range"].ToString();
var openEnded = rangeHeader.EndsWith("-", StringComparison.Ordinal);

var (start, intendedEnd) = ParseRange(context.HttpContext.Request, total);

var end = openEnded
? Math.Min(start + poolBuf.Length - 1, total - 1)
: intendedEnd;

var length = end - start + 1;
response.StatusCode = (start == 0 && end == total - 1) ? 200 : 206;
response.Headers["Content-Range"] = $"bytes {start}-{end}/{total}";
response.Headers["Content-Length"] = length.ToString();

fs.Seek(start, SeekOrigin.Begin);
var ct = context.HttpContext.RequestAborted;
long remaining = length;

while (remaining > 0 && !ct.IsCancellationRequested)
{
int toRead = (int)Math.Min(poolBuf.Length, remaining);
int read = await fs.ReadAsync(poolBuf, 0, toRead, ct);
if (read == 0) break;

await response.Body.WriteAsync(poolBuf, 0, read, ct);
await response.Body.FlushAsync(ct);
remaining -= read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(poolBuf);
}
}
also I noticed for some reason the bytes requested in Range header implied it requested for full video at once, the value of Range header was often like "bytes=73007104-"
wasabi
wasabi18h ago
Well that's the client's fault

Did you find this page helpful?