【问题标题】:Streaming video from an external service从外部服务流式传输视频
【发布时间】:2015-09-01 15:03:56
【问题描述】:

我正在处理一个项目(服务器端),我需要将数据(视频、大文件)流式传输到客户端。

使用ByteRangeStreamContent 完美运行,因为我从磁盘提供文件并且可以创建可搜索流 (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})。

当然,不可能将整个文件下载到内存中,然后将MemoryStream 用于ByteRangeStreamContent,原因很明显(太多的并发下载会在某个时候消耗所有可用内存)。

我发现这篇文章https://vikingerik.wordpress.com/2014/09/28/progressive-download-support-in-asp-net-web-api/作者说:

我的图书馆收到的更改请求是支持仅阅读 必要的数据并将其发送出去,而不是为 完整的数据。我不确定这会买什么,直到用户 指出他们正在从 WCF 流中读取资源数据 不支持查找,需要读取整个流 进入 MemoryStream 以允许库生成 输出。

此特定对象中仍然存在该限制,但存在 解决方法。而不是使用 ByteRangeStreamContent,你可以 而是使用 ByteArrayContent 对象。由于大多数 RANGE 请求将针对单个开始和结束字节,您可以拉 HttpRequestMessage 的范围,仅检索您的字节 需要并将其作为字节流发送回来。您还需要添加 CONTENT-RANGE 标头并将响应代码设置为 206 (PartialContent) 但这可能是一个可行的选择(尽管我 没有测试过)对于不想要或不能轻易获得的用户 兼容的流对象。

所以,我的问题基本上是:我该怎么做?

【问题讨论】:

    标签: c# asp.net-web-api stream


    【解决方案1】:

    我终于成功了。

    方法如下:

    stream 的自定义实现:

    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;
    

    【讨论】:

    • 不幸的是,这对我不起作用。尝试在 Asp.Net MVC 应用程序(.Net framework 4.6.2)中使用,url 到 azure blob 存储视频文件。在前端简单的 html 视频标签中,将控制器方法的路径作为源并输入“video/mp4”。
    猜你喜欢
    • 2019-02-25
    • 2011-01-14
    • 1970-01-01
    • 2013-12-26
    • 2012-10-02
    • 1970-01-01
    • 2016-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多