当我创建一个前端为 React 、后端为 .NET Core 7.0 的电子学习 Web 应用程序时,我偶然发现了一个对我来说很困难的主题。假设我有授权用户想要观看1小时长的视频,重量很大(比方说30GB) - 在反应中我可能会使用这样的东西:
const [videoUrl, setVideoUrl] = useState<string>();
axiosPrivate
.get(`/SomeVideoService/Video?id=${id}`, { responseType: "blob" })
.then((response) => {
const videoBlobUrl = URL.createObjectURL(response.data);
setVideoUrl(videoBlobUrl);
})
.catch((error) => {
console.error("Error fetching video:", error);
});
return (
<div>
<video width="640" height="360" controls>
<source src={videoUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
服务是这样写的:
[HttpGet("Video")]
[Authorize(Roles = "FirstRole, SecondRole, ThirdRole")]
public async Task<IActionResult> GetVideo(string id)
{
var video = "video.mp4";
var filePath = Path.Combine(Directory.GetCurrentDirectory().ToString(), "Videos", video);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var memory = new MemoryStream();
using (var stream = new FileStream(filePath, FileMode.Open))
{
stream.CopyTo(memory);
}
memory.Position = 0;
return File(memory, "video/mp4", Path.GetFileName(filePath));
}
但是我不确定这一切的作用 - 我看到了结果并且它正确地显示在我的屏幕上(我没有 1 小时长的视频,只有 1 分钟)。在网络选项卡中,我看到多个数据:图像/请求,状态代码附近写入 200 OK(来自内存缓存)。问题如下:
[HttpGet("Video")]
[Authorize(Roles = "Common, Extended")]
public async Task<IActionResult> GetVideo(string id)
{
var video = "video.mp4";
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Videos", video);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var fileInfo = new FileInfo(filePath);
long fileLength = fileInfo.Length;
var lastModified = fileInfo.LastWriteTimeUtc;
var entityTag = new EntityTagHeaderValue($"\"{fileInfo.LastWriteTimeUtc.Ticks}\"");
var requestHeaders = Request.GetTypedHeaders();
var responseHeaders = Response.GetTypedHeaders();
responseHeaders.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(3600)
};
responseHeaders.LastModified = lastModified;
responseHeaders.ETag = entityTag;
if (requestHeaders.IfModifiedSince.HasValue && requestHeaders.IfModifiedSince.Value >= lastModified)
{
return StatusCode(StatusCodes.Status304NotModified);
}
if (requestHeaders.IfNoneMatch != null && requestHeaders.IfNoneMatch.Contains(entityTag))
{
return StatusCode(StatusCodes.Status304NotModified);
}
var range = requestHeaders.Range;
if (range == null || !RangeHelper.TryGetRangeItem(range, fileLength, out var rangeItem))
{
return File(System.IO.File.OpenRead(filePath), "video/mp4", enableRangeProcessing: false);
}
var start = rangeItem.From ?? 0;
var end = rangeItem.To ?? (fileLength - 1);
var contentLength = end - start + 1;
var contentRange = new ContentRangeHeaderValue(start, end, fileLength);
Response.StatusCode = StatusCodes.Status206PartialContent;
responseHeaders.ContentRange = contentRange;
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
fileStream.Seek(start, SeekOrigin.Begin);
return File(fileStream, "video/mp4", enableRangeProcessing: true, fileDownloadName: null, lastModified, entityTag, contentLength);
}
或者,如果我想执行此方法,那么我应该将 1 小时长的视频剪切成更小的(10 秒)文件,并通过此端点为它们提供服务,并以某种方式在前端执行此操作(相同的反应片段将用于不剪切)版本):
import React, { useState, useEffect } from 'react';
const VideoPlayer = ({ videoId }) => {
const [videoUrl, setVideoUrl] = useState('');
useEffect(() => {
const apiUrl = `SomeVideoService/Video${videoId}`;
setVideoUrl(apiUrl);
}, [videoId]);
if (!videoId) {
return <div>Loading...</div>;
}
return (
<div>
<video width="640" height="360" controls>
<source src={videoUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};
export default VideoPlayer;
抱歉内容太长,希望有人有足够的耐心帮我写下来。感谢您阅读所有内容并与本文互动:)
带有
MemoryStream
的第一个代码示例对于您的用例来说非常糟糕,因为它在提供服务之前将整个视频加载到服务器上的内存中。即使对于单个请求,这也将是巨大的资源量,更不用说多个请求了。
需要支持 HTTP Range。使用 HTTP 范围,您不必担心将视频文件分成块,因为 Web 浏览器会自动请求它想要的文件部分,并且服务器将仅使用该范围内的文件中的字节进行适当响应。当用户浏览视频或“寻找”视频时,浏览器将自动发送必要的 HTTP 范围标头。
但是,我认为在采用这种方法之前您应该暂停一下。这样做只是一种美化的(但效率惊人的)下载协议。 1 小时 30GB 相当大,许多用户没有足够的带宽来观看它。
这就是像
HLS这样的协议存在的原因。巧合的是,HLS 是建立在您之前提到的“块”概念之上的。但是有很多库不仅可以对视频进行编码,还可以对播放器等进行编码。许多浏览器和平台已经内置支持它,尤其是最近(几年前我处理视频时这将是天赐之物!)。 HLS 更为复杂,只有当您想要获得好处时才应该走这条路。它需要以多个比特率对视频进行编码/分块。但这就是好处;质量将根据用户的可用带宽无缝变化。如果您有大量内容并且想要在各种网络连接质量下工作,这可能是必要的。
对于授权,是的,这只是您在后端服务中处理的事情。如果他们未经授权,那么您只需回复 401 即可。
您关于“前端服务器”的问题有点令人困惑。除非您不遗余力地拥有前端微服务,否则您通常只会从与其他服务器相同的服务器为前端提供服务。
除非您指的是前端开发服务器,例如 Webpack 和 Vite 等提供的服务器。但您不会在生产中运行这些服务器。