以现有实际解决方案为例,正确处理视频流
Posted
技术标签:
【中文标题】以现有实际解决方案为例,正确处理视频流【英文标题】:Handling video streaming correctly on the example of existing real-world solutions 【发布时间】:2021-09-27 12:09:17 【问题描述】:我正在试验/建立一个具有流媒体功能的网站,用户可以在其中上传视频 (.mp4
) 并在页面上查看视频(视频内容部分/增量返回)。我正在使用Azure Blob Storage
和dotnet
技术栈。
html:
<video src="https://localhost:5001/Files/DownloadFileStream?fileName=VID_20210719_110859.mp4" controls="true" />
控制器:
[HttpGet]
public async Task<IActionResult> DownloadFileStream([FromQuery] string fileName)
var range = Request.Headers["range"].ToString();
Console.WriteLine(range);
var container = new BlobContainerClient("UseDevelopmentStorage=true", "sample-container");
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(fileName);
var response = await blobClient.GetPropertiesAsync();
string contentType = response.Value.ContentType;
long contentLength = response.Value.ContentLength;
Azure.Response<BlobDownloadStreamingResult> result = await blobClient.DownloadStreamingAsync();
return new FileStreamResult(result.Value.Content, contentType)
EnableRangeProcessing = true,
;
// following approach supports video Seek functionality:
/*
using var memory = new MemoryStream();
await blobClient.DownloadToAsync(memory);
return new FileContentResult(memory.ToArray(), contentType)
EnableRangeProcessing = true,
;*/
我注意到的是,当我流式传输内容时 - 在 Chrome 的 Inspect mode
中,我们可以看到正在下载的媒体文件。事实上,如果你使用 seek 功能 - 会出现多条请求记录:
我目前的问题是,在文件完全下载/播放到流结束后 - 如果您再次开始播放 - 浏览器 将再次开始下载内容,而不是已经使用缓存/下载的内容。
因此,我开始调查 YouTube 和 Instagram 等巨大的市场领导者如何处理视频流,但我注意到在视频播放时 - 没有出现单一的“媒体”类型的内容/请求。
事实上,从inspector
的角度来看 - 似乎根本没有下载任何内容。所以这引发了一些问题。
-
YouTube 和 Instagram 等市场领导者如何向客户流式传输视频,而不暴露于
inspector
任何媒体流量? .Net 5
是否可以复制此行为?
为什么我的应用程序会一遍又一遍地重新下载同一个文件,如何防止这种情况发生?
为什么在使用DownloadStreamingAsync()
方法时搜索功能不起作用,如何解决?我使用这种方法主要是因为在这种情况下应用程序的内存占用更小。在将内容返回给客户端之前,我们不必将整个流下载到内存中。
虽然我在这里列出了 3 个问题 - 我主要关心的是第一个问题,因此非常欢迎任何关于该主题的答案。 :)
【问题讨论】:
【参考方案1】:好的,经过一番研究和折腾 - 我找到了一篇关于这个主题的好文章:
How video streaming works on the web: An introduction
https://medium.com/canal-tech/how-video-streaming-works-on-the-web-an-introduction-7919739f7e1
简短的故事是 - 您可以编写一个将 MediaSource 注入标签的 javascript 代码,而不是将源文件直接提供给 <video>
html
标签。然后,您可以使用Fetch API
或XMLHttpRequest
之类的技术编写自定义逻辑,将视频流注入缓冲区。在XMLHttpRequest
类型的技术的情况下,您似乎会看到xhr
类型的请求,而在Fetch API
- fetch
类型的请求中检查器.. 这个主题更加复杂和复杂,我对它没有广泛的了解。部分原因是我不是 JS 开发人员 :) 还因为您必须了解编解码器/编码器/视频数据帧的工作原理等。
关于上述技术的更多细节:
Fetch API vs XMLHttpRequest
您可以研究的另一个值得注意的主题是流格式/标准。例如,您可以使用自适应流技术根据带宽容量调整视频质量。两个流媒体标准是HLS
(旧)和DASH
(新?)。
此外,既然我们正在做这件事 - 我建议研究高级视频播放器库:
video.js:https://github.com/videojs/video.js hls.js:https://github.com/video-dev/hls.js/ dash.js:https://github.com/Dash-Industry-Forum/dash.js/如果您在 github 上查找它们 - 您将对整个主题有所了解。
编辑 11.08.2021
关于我自己关于DownloadStreamingAsync
方法使用的问题并回答@Nate 的问题 - 你不能使用这种方法。 Download
的意思是 - 给我完整的内容。你正在寻找的是 OpenReadAsync
方法,它可以让你获得流。
这是一个工作示例(我自己不会使用,但有人可能会觉得它有帮助):
[HttpGet]
[Route("Stream4/fileName")]
public async Task<IActionResult> Get3(
[FromRoute] string fileName,
CancellationToken ct)
var container = new BlobContainerClient("UseDevelopmentStorage=true", "sample-container");
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(fileName);
var response = await blobClient.GetPropertiesAsync();
string contentType = response.Value.ContentType;
long contentLength = response.Value.ContentLength;
long kBytesToReadAtOnce = 300;
long bytesToReadAtOnce = kBytesToReadAtOnce * 1024;
var result = await blobClient.OpenReadAsync(0, bufferSize: (int)bytesToReadAtOnce, cancellationToken: ct);
return new FileStreamResult(result, contentType)
// this is important
EnableRangeProcessing = true,
;
【讨论】:
> "为什么使用DownloadStreamingAsync()方法时seek功能不起作用以及如何解决?我使用这种方法主要是因为应用程序在这种情况下占用的内存较小。我们不在将内容返回给客户端之前,必须将整个流下载到内存中。”你找到这个问题的答案了吗?我也想知道使用带有 seek 的 DownloadStreamingAsync 的解决方案 @NateRadebaugh 是的,我找到了答案。检查编辑的帖子。【参考方案2】:这是我必须要做的事情。最终,它使用 blob 并使用请求中的范围标头仅下载文件的一部分。
由于没有自动支持 Azure Blob 的 EnableRangeProcessing
的神奇 PhysicalFile
-esc 帮助程序,我们手动完成。
浏览器查找要返回的 206: Partial Content
标头,连同 Accept-Ranges: bytes
、Content-Length
和 Content-Range
响应标头,以便知道是否还有更多视频要下载。
[HttpGet, Route("video")]
public async Task<IActionResult> VideoAsync()
var rangeHeaders = Request.GetTypedHeaders().Range;
var blobServiceClient = new BlobServiceClient(connectionString);
var containerClient = blobServiceClient.GetBlobContainerClient("data");
var blobClient = containerClient.GetBlobClient(fileName);
if (blobClient.Exists())
var getPropertiesResponse = await blobClient.GetPropertiesAsync();
string contentType = getPropertiesResponse.Value.ContentType;
long contentLength = getPropertiesResponse.Value.ContentLength;
Azure.HttpRange? range = null;
if (rangeHeaders?.Ranges?.Count > 0)
var rangeHeader = rangeHeaders.Ranges.First();
// 1. If the unit is not 'bytes'.
// 2. If there are multiple ranges in header value.
// 3. If start or end position is greater than file length.
if (rangeHeaders.Unit != "bytes" || rangeHeaders.Ranges.Count > 1)
Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable;
Response.Headers.Add("Content-Range", $"bytes range.Value.Offset-contentLength - 1/contentLength");
Response.Headers.Add("Content-Type", "video/mp4");
return null;
var offset = rangeHeader.From ?? 0;
var length = rangeHeader.From.HasValue && rangeHeader.To.HasValue
? rangeHeader.To - rangeHeader.From
: null;
range = new Azure.HttpRange(offset, length);
// Append appropriate response headers based on status
Response.Headers.Add("Accept-Ranges", "bytes");
Response.Headers.Add("Content-Length", $"contentLength");
if (range.HasValue)
Response.Headers.Add("Content-Range", $"bytes range.Value.Offset-range.Value.Length ?? (contentLength - 1)/contentLength");
var streamingFileResponse = await blobClient.DownloadStreamingAsync(range ?? default);
var videoContent = streamingFileResponse.Value.Content;
var videoMeta = streamingFileResponse.Value.Details;
var fileResponse = new FileStreamResult(videoContent, "video/mp4");
Response.StatusCode = (int)HttpStatusCode.PartialContent;
return fileResponse;
throw new BadHttpRequestException("Bad request");
【讨论】:
以上是关于以现有实际解决方案为例,正确处理视频流的主要内容,如果未能解决你的问题,请参考以下文章
stm32 播放高帧率高分辨率视频和照片详细制作过程(播放Bad Apple为例)