流式传输来自外部服务的视频 [英] Streaming video from an external service
问题描述
我正在一个项目(服务器端)上工作,我需要将数据(视频,大文件)流式传输到客户端.
I am working on a project (server side) where i need to stream data (videos, large files) to clients.
使用 ByteRangeStreamContent
可以完美工作,因为我正在从磁盘提供文件,并且可以创建可搜索的流( FileStream
).
This worked perfect using ByteRangeStreamContent
, as i was serving files from disk and could create a seekable stream (FileStream
).
if (Request.Headers.Range != null)
{
try
{
HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
partialResponse.Content = new ByteRangeStreamContent(fs, Request.Headers.Range, mediaType);
return partialResponse;
}
catch (InvalidByteRangeException invalidByteRangeException)
{
return Request.CreateErrorResponse(invalidByteRangeException);
}
}
else
{
response.Content = new StreamContent(fs);
response.Content.Headers.ContentType = mediaType;
return response;
}
但是,我将文件提供程序从磁盘移到了外部服务.该服务使我可以获取数据块(范围{0}-{1}).
But, i moved the file provider from disk to an external service. The service allows me to get chunks of data (Range{0}-{1}).
当然,不可能将整个文件下载到内存中,然后将 MemoryStream
用于 ByteRangeStreamContent
,这是有明显原因的(太多的并行下载会消耗所有的可用内存).
Of course, it's not possible to download whole file in memory and then use a MemoryStream
for ByteRangeStreamContent
because of the obvious reasons (too many concurrent downloads will consume all the available memory at some point).
我找到了这篇文章 https://vikingerik.wordpress.com/2014/09/28/progressive-download-support-in-asp-net-web-api/作者说:
I found this article https://vikingerik.wordpress.com/2014/09/28/progressive-download-support-in-asp-net-web-api/ where the author says:
我对图书馆提出的更改请求是仅支持阅读必要的数据并发送出去,而不是为完整的数据.我不确定用户会买什么指出他们正在从WCF流中读取资源数据这不支持搜索,需要阅读整个信息流进入MemoryStream以便允许库生成输出.
A change request I got for my library was to support reading only the necessary data and sending that out rather than opening a stream for the full data. I wasn’t sure what this would buy until the user pointed out they are reading their resource data from a WCF stream which does not support seeking and would need to read the whole stream into a MemoryStream in order to allow the library to generate the output.
该限制在该特定对象中仍然存在,但存在一个限制解决方法.您可以使用ByteRangeStreamContent而不是而是使用ByteArrayContent对象.由于多数RANGE请求将针对单个开始和结束字节,您可以拉HttpRequestMessage的范围,仅检索您所需要的字节需要并将其作为字节流发送回去.您还需要添加CONTENT-RANGE标头,并将响应代码设置为206(PartialContent),但这可能是一个可行的选择(尽管我尚未测试过的产品),供那些不希望或无法轻易获得产品的用户使用兼容的流对象.
That limitation still exists in this specific object but there is a workaround. Instead of using a ByteRangeStreamContent, you could instead use a ByteArrayContent object instead. Since the majority of RANGE requests will be for a single start and end byte, you could pull the range from the HttpRequestMessage, retrieve only the bytes you need and send it back out as a byte stream. You’ll also need to add the CONTENT-RANGE header and set the response code to 206 (PartialContent) but this could be a viable alternative (though I haven’t tested it) for users who do not want or can’t easily get a compliant stream object.
所以,我的问题基本上是:我该怎么做?
So, my question basically is: how can i do that ?
推荐答案
我终于设法做到了.
方法如下:
流
的自定义实现:
public class BufferedHTTPStream : Stream
{
private readonly Int64 cacheLength = 4000000;
private const Int32 noDataAvaiable = 0;
private MemoryStream stream = null;
private Int64 currentChunkNumber = -1;
private Int64? length;
private Boolean isDisposed = false;
private Func<long, long, Stream> _getStream;
private Func<long> _getContentLength;
public BufferedHTTPStream(Func<long, long, Stream> streamFunc, Func<long> lengthFunc)
{
_getStream = streamFunc;
_getContentLength = lengthFunc;
}
public override Boolean CanRead
{
get
{
EnsureNotDisposed();
return true;
}
}
public override Boolean CanWrite
{
get
{
EnsureNotDisposed();
return false;
}
}
public override Boolean CanSeek
{
get
{
EnsureNotDisposed();
return true;
}
}
public override Int64 Length
{
get
{
EnsureNotDisposed();
if (length == null)
{
length = _getContentLength();
}
return length.Value;
}
}
public override Int64 Position
{
get
{
EnsureNotDisposed();
Int64 streamPosition = (stream != null) ? stream.Position : 0;
Int64 position = (currentChunkNumber != -1) ? currentChunkNumber * cacheLength : 0;
return position + streamPosition;
}
set
{
EnsureNotDisposed();
EnsurePositiv(value, "Position");
Seek(value);
}
}
public override Int64 Seek(Int64 offset, SeekOrigin origin)
{
EnsureNotDisposed();
switch (origin)
{
case SeekOrigin.Begin:
break;
case SeekOrigin.Current:
offset = Position + offset;
break;
default:
offset = Length + offset;
break;
}
return Seek(offset);
}
private Int64 Seek(Int64 offset)
{
Int64 chunkNumber = offset / cacheLength;
if (currentChunkNumber != chunkNumber)
{
ReadChunk(chunkNumber);
currentChunkNumber = chunkNumber;
}
offset = offset - currentChunkNumber * cacheLength;
stream.Seek(offset, SeekOrigin.Begin);
return Position;
}
private void ReadNextChunk()
{
currentChunkNumber += 1;
ReadChunk(currentChunkNumber);
}
private void ReadChunk(Int64 chunkNumberToRead)
{
Int64 rangeStart = chunkNumberToRead * cacheLength;
if (rangeStart >= Length) { return; }
Int64 rangeEnd = rangeStart + cacheLength - 1;
if (rangeStart + cacheLength > Length)
{
rangeEnd = Length - 1;
}
if (stream != null) { stream.Close(); }
stream = new MemoryStream((int)cacheLength);
var responseStream = _getStream(rangeStart, rangeEnd);
responseStream.Position = 0;
responseStream.CopyTo(stream);
responseStream.Close();
stream.Position = 0;
}
public override void Close()
{
EnsureNotDisposed();
base.Close();
if (stream != null) { stream.Close(); }
isDisposed = true;
}
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
{
EnsureNotDisposed();
EnsureNotNull(buffer, "buffer");
EnsurePositiv(offset, "offset");
EnsurePositiv(count, "count");
if (buffer.Length - offset < count) { throw new ArgumentException("count"); }
if (stream == null) { ReadNextChunk(); }
if (Position >= Length) { return noDataAvaiable; }
if (Position + count > Length)
{
count = (Int32)(Length - Position);
}
Int32 bytesRead = stream.Read(buffer, offset, count);
Int32 totalBytesRead = bytesRead;
count -= bytesRead;
while (count > noDataAvaiable)
{
ReadNextChunk();
offset = offset + bytesRead;
bytesRead = stream.Read(buffer, offset, count);
count -= bytesRead;
totalBytesRead = totalBytesRead + bytesRead;
}
return totalBytesRead;
}
public override void SetLength(Int64 value)
{
EnsureNotDisposed();
throw new NotImplementedException();
}
public override void Write(Byte[] buffer, Int32 offset, Int32 count)
{
EnsureNotDisposed();
throw new NotImplementedException();
}
public override void Flush()
{
EnsureNotDisposed();
}
private void EnsureNotNull(Object obj, String name)
{
if (obj != null) { return; }
throw new ArgumentNullException(name);
}
private void EnsureNotDisposed()
{
if (!isDisposed) { return; }
throw new ObjectDisposedException("BufferedHTTPStream");
}
private void EnsurePositiv(Int32 value, String name)
{
if (value > -1) { return; }
throw new ArgumentOutOfRangeException(name);
}
private void EnsurePositiv(Int64 value, String name)
{
if (value > -1) { return; }
throw new ArgumentOutOfRangeException(name);
}
private void EnsureNegativ(Int64 value, String name)
{
if (value < 0) { return; }
throw new ArgumentOutOfRangeException(name);
}
}
用法:
var fs = new BufferedHTTPStream((start, end) =>
{
// return stream from external service
}, () =>
{
// return stream length from external service
});
HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
partialResponse.Content = new ByteRangeStreamContent(fs, Request.Headers.Range, mediaType);
partialResponse.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = fileName
};
return partialResponse;
这篇关于流式传输来自外部服务的视频的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!