我正在尝试从HttpHandler流式传输webforms中的大文件。它似乎不起作用,因为它不流式传输文件。而是将文件读入内存然后将其发送回客户端。我全神贯注地寻找解决方案,解决方案告诉我他们在执行同样的操作时会流式传输文件。我的解决方案就是这个:
using (Stream fileStream = File.OpenRead(path))
{
context.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(360.0));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.AppendHeader("Content-Type", "video/mp4");
context.Response.AppendHeader("content-length", file.Length);
byte[] buffer = new byte[1024];
while (true)
{
if (context.Response.IsClientConnected)
{
int bytesRead = fileStream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
context.Response.OutputStream.Write(buffer, 0, bytesRead);
context.Response.Flush();
}
else
{
break;
}
}
context.Response.End();
}
如果我调试代码,小文件会发生什么,它会播放视频,但直到它到达context.Respond.End()行。但是对于大文件,这不起作用,因为它将整个文件存储在内存中会带来问题。
答案 0 :(得分:11)
我遇到了类似的问题,在播放之前必须完全下载视频。
我可以看到你想要流式传输视频,更具体一点。 你必须要小心编码(确保它是可流动的),不要只依赖于扩展,因为创建文件的人可能以一种奇怪的方式构建视频,但99%的时间你应该好。我使用mediainfo。 在你的情况下应该是H.264。
它还取决于浏览器以及您用于流式传输的内容(除了后端代码)。就我而言,我使用了Chrome / Html5和.webm(VP8 / Ogg Vorbis)。它适用于超过1G的文件。没有测试大于4G ...
我用于下载视频的代码:
public void Video(string folder, string name) {
string filepath = Server.MapPath(String.Format("{0}{1}", HttpUtility.UrlDecode(folder), name));
string filename = name;
System.IO.Stream iStream = null;
byte[] buffer = new Byte[4096];
int length;
long dataToRead;
try {
// Open the file.
iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
System.IO.FileAccess.Read, System.IO.FileShare.Read);
// Total bytes to read:
dataToRead = iStream.Length;
Response.AddHeader("Accept-Ranges", "bytes");
Response.ContentType = MimeType.GetMIMEType(name);
int startbyte = 0;
if (!String.IsNullOrEmpty(Request.Headers["Range"])) {
string[] range = Request.Headers["Range"].Split(new char[] { '=', '-' });
startbyte = Int32.Parse(range[1]);
iStream.Seek(startbyte, SeekOrigin.Begin);
Response.StatusCode = 206;
Response.AddHeader("Content-Range", String.Format(" bytes {0}-{1}/{2}", startbyte, dataToRead - 1, dataToRead));
}
while (dataToRead > 0) {
// Verify that the client is connected.
if (Response.IsClientConnected) {
// Read the data in buffer.
length = iStream.Read(buffer, 0, buffer.Length);
// Write the data to the current output stream.
Response.OutputStream.Write(buffer, 0, buffer.Length);
// Flush the data to the HTML output.
Response.Flush();
buffer = new Byte[buffer.Length];
dataToRead = dataToRead - buffer.Length;
} else {
//prevent infinite loop if user disconnects
dataToRead = -1;
}
}
} catch (Exception ex) {
// Trap the error, if any.
Response.Write("Error : " + ex.Message);
} finally {
if (iStream != null) {
//Close the file.
iStream.Close();
}
Response.Close();
}
}
确保您的回复标题包含您需要的所有内容。
答案 1 :(得分:0)
这里真正重要的是'Range'标头。尽管现有答案是正确的,但没有任何解释。
在不指定范围的情况下发出请求时,将传输整个文件。视频播放器会根据视频中播放器的位置自动指定“范围”标头,并指定一个起始字节。
由于这本质上是HTTP的一部分,因此在RFC 7233.
中有很好的记录“接受范围:字节”标头告诉客户端我们要接受范围标头作为字节数。状态代码“ 206”告诉客户端我们发送了部分内容,也就是整个文件的一部分。 “ Content-Range:start-end / total”标头告诉客户端我们在当前请求中发回的信息范围。
这是一个功能齐全的代码段:
public static void RespondFile(this HttpListenerContext context, string path, bool download = false) {
HttpListenerResponse response = context.Response;
// tell the browser to specify the range in bytes
response.AddHeader("Accept-Ranges", "bytes");
response.ContentType = GetMimeType(path);
response.SendChunked = false;
// open stream to file we're sending to client
using(FileStream fs = File.OpenRead(path)) {
// format: bytes=[start]-[end]
// documentation: https://tools.ietf.org/html/rfc7233#section-4
string range = context.Request.Headers["Range"];
long bytes_start = 0,
bytes_end = fs.Length;
if (range != null) {
string[] range_info = context.Request.Headers["Range"].Split(new char[] { '=', '-' });
bytes_start = Convert.ToInt64(range_info[1]);
if (!string.IsNullOrEmpty(range_info[2]))
bytes_end = Convert.ToInt64(range_info[2]);
response.StatusCode = 206;
response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", bytes_start, bytes_end - 1, fs.Length));
}
// determine how many bytes we'll be sending to the client in total
response.ContentLength64 = bytes_end - bytes_start;
// go to the starting point of the response
fs.Seek(bytes_start, SeekOrigin.Begin);
// setting this header tells the browser to download the file
if (download)
response.AddHeader("content-disposition", "attachment; filename=" + Path.GetFileName(path));
// stream video to client
// note: closed connection during transfer throws exception
byte[] buffer = new byte[HttpServer.BUFFER_SIZE];
int bytes_read = 0;
try {
while (fs.Position < bytes_end) {
bytes_read = fs.Read(buffer, 0, buffer.Length);
response.OutputStream.Write(buffer, 0, bytes_read);
}
response.OutputStream.Close();
} catch(Exception) {}
}
}
请注意,我们可以简单地检查文件流的“位置”(以字节为单位),而不用跟踪已经总共发送了多少个字节。
答案 2 :(得分:0)
Maxad的答案是一个完美的答案。我还对.Net Core版本进行了一些更改:
<video id="myvideo" height="400" width="600" controls>
<source src="../api/StreamApi/GetStream" type="video/mp4"/>
</video>
[Route("api/StreamApi/GetStream")]
[HttpGet]
public async Task GetStream()
{
string filepath = @"C:\temp\car.mp4";
string filename = Path.GetFileName(filepath);
System.IO.Stream iStream = null;
byte[] buffer = new Byte[4096];
int length;
long dataToRead;
try
{
// Open the file.
iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
System.IO.FileAccess.Read, System.IO.FileShare.Read);
// Total bytes to read:
dataToRead = iStream.Length;
Response.Headers["Accept-Ranges"] = "bytes";
Response.ContentType = "application/octet-stream";
int startbyte = 0;
if (!String.IsNullOrEmpty(Request.Headers["Range"]))
{
string[] range = Request.Headers["Range"].ToString().Split(new char[] { '=', '-' });
startbyte = Int32.Parse(range[1]);
iStream.Seek(startbyte, SeekOrigin.Begin);
Response.StatusCode = 206;
Response.Headers["Content-Range"] = String.Format(" bytes {0}-{1}/{2}", startbyte, dataToRead - 1, dataToRead);
}
var outputStream = this.Response.Body;
while (dataToRead > 0)
{
// Verify that the client is connected.
if (HttpContext.RequestAborted.IsCancellationRequested == false)
{
// Read the data in buffer.
length = await iStream.ReadAsync(buffer, 0, buffer.Length);
// Write the data to the current output stream.
await outputStream.WriteAsync(buffer, 0, buffer.Length);
// Flush the data to the HTML output.
outputStream.Flush();
buffer = new Byte[buffer.Length];
dataToRead = dataToRead - buffer.Length;
}
else
{
//prevent infinite loop if user disconnects
dataToRead = -1;
}
}
}
catch (Exception ex)
{
// Trap the error, if any.
}
finally
{
if (iStream != null)
{
//Close the file.
iStream.Close();
}
Response.Clear();
}
}