我正在尝试在ASP.NET Core中创建一个IActionResult来处理range requests以向HTML5媒体播放器提供内容。
我认为我得到了实现,并且在使用cURL或wget这样的基本用户代理调用时似乎正常工作,但是当Chrome调用它来获取音频代码的内容时,它就无法运行。
我手动检查了返回的内容和物理文件,以确保我提供正确的内容,它似乎正在运作。
全内容请求检查:
dd ibs=1 if=CA98AF68-766B-457C-828E-DB3B8C2A57BE.webm 2> /dev/null | \
openssl dgst -sha256 -binary | \
openssl enc -base64 -A
80AE1Im8ebN12f3te7UvBcug7DkGluam21wgL7zJ4h4=
curl --range 0- --silent http://localhost:3038/Media/CA98AF68-766B-457C-828E-DB3B8C2A57BE/webm | \
openssl dgst -sha256 -binary | \
openssl enc -base64 -A
80AE1Im8ebN12f3te7UvBcug7DkGluam21wgL7zJ4h4=
远程内容请求检查:
dd ibs=1 if=CA98AF68-766B-457C-828E-DB3B8C2A57BE.webm count=1024 2> /dev/null | \
openssl dgst -sha256 -binary | \
openssl enc -base64 -A
SFAoua0OkQ3ZbEnXXd42CqcucQdkOTxpkjQwBNKOF7Y=
curl --range 0-1024 --silent http://localhost:3038/Media/CA98AF68-766B-457C-828E-DB3B8C2A57BE/webm \
openssl dgst -sha256 -binary | \
openssl enc -base64 -A
SFAoua0OkQ3ZbEnXXd42CqcucQdkOTxpkjQwBNKOF7Y=
物理文件块的哈希值和ASP.NET返回的内容对于完整文件和部分文件都是相同的,所以我认为代码是正确的。
然而,Chrome正在调用与获取随机数据完全相同的URI:有时更多,有时更少,有时会引发请求中止并且任务取消异常抛出。
返回80.2千字节的请求:
请求返回196个字节:
返回132千字节的请求:
任务取消了引发的异常:
实现非常简单,它支持(目前)只有单范围请求而且没有铃声或口哨声(以下代码是一个简单的沙箱,绝不是生产准备好的。)
这是远程操作结果实现:
namespace Sandbox.Results
{
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Microsoft.Net.Http.Headers;
public class StreamingActionResult : IActionResult, IDisposable
{
private readonly string contentType;
private readonly FileStream stream;
public StreamingActionResult(IFileInfo fileInfo, string contentType)
{
this.stream = new FileStream(fileInfo.PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous);
this.contentType = contentType;
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.Headers.Add("Accept-Ranges", "bytes");
RequestHeaders requestHeaders = context.HttpContext.Request.GetTypedHeaders();
if (requestHeaders.Range == null)
{
context.HttpContext.Response.ContentLength = this.stream.Length - this.stream.Position;
context.HttpContext.Response.ContentType = this.contentType;
context.HttpContext.Response.StatusCode = (int) HttpStatusCode.PartialContent;
await this.stream.CopyToAsync(context.HttpContext.Response.Body, 4096, context.HttpContext.RequestAborted);
return;
}
if (requestHeaders.Range.Unit != "bytes" || requestHeaders.Range.Ranges.Count != 1)
{
context.HttpContext.Response.StatusCode = (int) HttpStatusCode.RequestedRangeNotSatisfiable;
return;
}
RangeItemHeaderValue range = requestHeaders.Range.Ranges.First();
this.stream.Seek(range.From.Value, SeekOrigin.Begin);
context.HttpContext.Response.ContentType = this.contentType;
context.HttpContext.Response.StatusCode = (int) HttpStatusCode.PartialContent;
if (range.To.HasValue)
{
context.HttpContext.Response.ContentLength = range.To.Value - range.From.Value;
context.HttpContext.Response.Headers.Add("Content-Range", $"bytes {range.From.Value:D}-{range.To.Value:D}/{this.stream.Length:D}");
await StreamCopyOperation.CopyToAsync(this.stream, context.HttpContext.Response.Body, range.To.Value - range.From.Value, 4096, context.HttpContext.RequestAborted);
}
else
{
context.HttpContext.Response.ContentLength = this.stream.Length - range.From.Value;
context.HttpContext.Response.Headers.Add("Content-Range", $"bytes {range.From.Value:D}-{this.stream.Length:D}/{this.stream.Length:D}");
await this.stream.CopyToAsync(context.HttpContext.Response.Body, 4096, context.HttpContext.RequestAborted);
}
}
protected virtual void Dispose(bool isDisposing)
{
if (isDisposing)
{
this.stream.Dispose();
}
}
}
}
这是使用行动结果的方式:
namespace Sandbox.Controllers
{
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Sandbox.Results;
[Route("/Media")]
public class MediaController : Controller
{
private readonly IHostingEnvironment environment;
public MediaController(IHostingEnvironment environment)
{
this.environment = environment;
}
[HttpGet("{id}/{format}")]
public IActionResult Get([FromRoute] Guid id, [FromRoute] string format)
{
IFileInfo fileInfo = this.environment.ContentRootFileProvider.GetFileInfo($"Media\\{id:D}.{format}");
return new StreamingActionResult(fileInfo, $"audio/{format}");
}
}
}
这就是媒体元素的宣告方式:
<!doctype html>
<html>
<head>
<title>Sandbox</title>
</head>
<body>
<h1>Sandbox</h1>
<audio controls preload="metadata">
<source src="/Media/CA98AF68-766B-457C-828E-DB3B8C2A57BE/webm" type="audio/webm" />
<p>Your browser does not support audio content.</p>
</audio>
</body>
</html>
我无法理解错误,如果它是我的代码中的内容,Chrome中的内容或其他内容。