Stream wrapper to make Stream seekable?
Asked Answered
W

6

30

I have a readonly System.IO.Stream implementation that is not seekable (and its Position always returns 0). I need to send it to a consumer that does some Seek operations (aka, sets the Position) on the stream. It's not a huge seek -- say +/- 100 from the current position. Is there an existing Stream wrapper that will add a buffering ability to the stream for simple Seek operations?

Update: I should add that my consumer is the NAudio Mp3FileReader. I really just need a way to play a (slowly and indefinitely) streaming MP3. I think it's a bug that NAudio expects to be able to seek their data source at will.

Warbler answered 23/10, 2012 at 17:22 Comment(6)
MemoryStream ? BufferedStream ?Mutinous
If you look at the implementation of BufferedStream, you will see that it does not actually allow seek operations when the base stream does not allow them. MemoryStream has no constructors taking a Stream parameter.Warbler
You can play from an MP3 network stream in NAudio, and the NAudioDemo app shows you how to do this. The Mp3FileReader is for reading files, and so expects them to be seekable. Creating a TOC ahead of time allows us to support very fast and accurate repositions into a VBR MP3. However, I agree it would be nice for a future NAudio to support non-seekable streams passed into the Mp3FileReader.Kelila
@MarkHeath I have tried to open very large file with Mp3FileReader, and it seems to be stuck forever while doing that. Is there an option NOT to build TOC while opening the file?Microstructure
@DanielMošmondor no, but it's open source, so feel free to mod it yourself. Without a TOC, the repositioning will be broken though. I'd like to update it to have a JIT TOC creation in the future so if you don't ask for the length or ask to reposition into the future, it doesn't need to look ahead.Kelila
@MarkHeath thanks, it seems that I'll have to do that since I need to a) quickly read all frames without decoding and calculate the file duration b) split mp3 files at frame boundaries, again, quickly...Microstructure
S
26

Seeking forwards is easy enough (just read), but you can't seek backwards without buffering. Maybe just:

using(var ms = new MemoryStream()) {
    otherStream.CopyTo(ms);
    ms.Position = 0;
    // now work with ms
}

This, however, is only suitable for small-to-moderate streams (not GB), that are known to end (which streams are not requires to do). If you need a larger stream, a FileStream to a temp-file would work, but is significantly more IO-intensive.

Surefooted answered 23/10, 2012 at 17:27 Comment(4)
This is an interesting approach. My input stream is an audio stream coming from a server. It has to stay open until the application closes, and my consumer will close itself if it runs out of data. I'm not quite seeing how to apply this approach.Warbler
@Warbler in that case you're probably going to implement it manually with a circular buffer for backwards seek (by a limited amount). Sorry - nothing inbuilt.Surefooted
I was able to approach the problem with an alternate plan. NAudio has a per-frame example for streaming situations.Warbler
Good answer, make sure to use CopyToAsync if your Stream is IO-bound or Network-bound.Palaeozoic
H
23

Here's a wrapper to make any Stream seekable for read operations.

It works by caching reads from the underlying stream, up to the number of bytes specified in the constructor. This will come in handy when memory constraints prohibit Marc Gravell's solution.

Supported seek operations:

  • seeking forward using SeekOrigin.Current and SeekOrigin.Begin works for arbitrary offsets
  • seeking backwards using SeekOrigin.Current and SeekOrigin.Begin works for down to -seekBackBufferSize bytes from the current position in the underlying stream (which may differ from readSeekableStream.Position after a previous backwards seek)
  • seeking using SeekOrigin.End works for offset >= -seekBackBufferSize && offset <= 0

General remarks

  • the Seek method and the Position property are completely handled internally and do not involve the underlying stream (which would only throw anyway)
  • seeking affects the read part of the stream only, hence the name of the class
  • all write operations are simply delegated through to the underlying stream
  • wrapping already seekable streams with this would be a waste of resources
  • some problems addressed by ReadSeekableStream below can also be solved by my PeekableStream class

This implementation is fresh and not yet battle-hardened. I have however unit tested it for quite a few seek/read cases and corner cases, and cross-compared it against a (freely seekable) MemoryStream.

public class ReadSeekableStream : Stream
{
    private long _underlyingPosition;
    private readonly byte[] _seekBackBuffer;
    private int _seekBackBufferCount;
    private int _seekBackBufferIndex;
    private readonly Stream _underlyingStream;

    public ReadSeekableStream(Stream underlyingStream, int seekBackBufferSize)
    {
        if (!underlyingStream.CanRead)
            throw new Exception("Provided stream " + underlyingStream + " is not readable");
        _underlyingStream = underlyingStream;
        _seekBackBuffer = new byte[seekBackBufferSize];
    }

    public override bool CanRead { get { return true; } }
    public override bool CanSeek { get { return true; } }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int copiedFromBackBufferCount = 0;
        if (_seekBackBufferIndex < _seekBackBufferCount)
        {
            copiedFromBackBufferCount = Math.Min(count, _seekBackBufferCount - _seekBackBufferIndex);
            Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferIndex, buffer, offset, copiedFromBackBufferCount);
            offset += copiedFromBackBufferCount;
            count -= copiedFromBackBufferCount;
            _seekBackBufferIndex += copiedFromBackBufferCount;
        }
        int bytesReadFromUnderlying = 0;
        if (count > 0)
        {
            bytesReadFromUnderlying = _underlyingStream.Read(buffer, offset, count);
            if (bytesReadFromUnderlying > 0)
            {
                _underlyingPosition += bytesReadFromUnderlying;

                var copyToBufferCount = Math.Min(bytesReadFromUnderlying, _seekBackBuffer.Length);
                var copyToBufferOffset = Math.Min(_seekBackBufferCount, _seekBackBuffer.Length - copyToBufferCount);
                var bufferBytesToMove = Math.Min(_seekBackBufferCount - 1, copyToBufferOffset);

                if (bufferBytesToMove > 0)
                    Buffer.BlockCopy(_seekBackBuffer, _seekBackBufferCount - bufferBytesToMove, _seekBackBuffer, 0, bufferBytesToMove);
                Buffer.BlockCopy(buffer, offset, _seekBackBuffer, copyToBufferOffset, copyToBufferCount);
                _seekBackBufferCount = Math.Min(_seekBackBuffer.Length, _seekBackBufferCount + copyToBufferCount);
                _seekBackBufferIndex = _seekBackBufferCount;
            }
        }
        return copiedFromBackBufferCount + bytesReadFromUnderlying;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        if (origin == SeekOrigin.End) 
            return SeekFromEnd((int) Math.Max(0, -offset));

        var relativeOffset = origin == SeekOrigin.Current
            ? offset
            : offset - Position;

        if (relativeOffset == 0)
            return Position;
        else if (relativeOffset > 0)
            return SeekForward(relativeOffset);
        else
            return SeekBackwards(-relativeOffset);
    }

    private long SeekForward(long origOffset)
    {
        long offset = origOffset;
        var seekBackBufferLength = _seekBackBuffer.Length;

        int backwardSoughtBytes = _seekBackBufferCount - _seekBackBufferIndex;
        int seekForwardInBackBuffer = (int) Math.Min(offset, backwardSoughtBytes);
        offset -= seekForwardInBackBuffer;
        _seekBackBufferIndex += seekForwardInBackBuffer;

        if (offset > 0)
        {
            // first completely fill seekBackBuffer to remove special cases from while loop below
            if (_seekBackBufferCount < seekBackBufferLength)
            {
                var maxRead = seekBackBufferLength - _seekBackBufferCount;
                if (offset < maxRead)
                    maxRead = (int) offset;
                var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead);
                _underlyingPosition += bytesRead;
                _seekBackBufferCount += bytesRead;
                _seekBackBufferIndex = _seekBackBufferCount;
                if (bytesRead < maxRead)
                {
                    if (_seekBackBufferCount < offset)
                        throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes");
                    return Position;
                }
                offset -= bytesRead;
            }

            // now alternate between filling tempBuffer and seekBackBuffer
            bool fillTempBuffer = true;
            var tempBuffer = new byte[seekBackBufferLength];
            while (offset > 0)
            {
                var maxRead = offset < seekBackBufferLength ? (int) offset : seekBackBufferLength;
                var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, maxRead);
                _underlyingPosition += bytesRead;
                var bytesReadDiff = maxRead - bytesRead;
                offset -= bytesRead;
                if (bytesReadDiff > 0 /* reached end-of-stream */ || offset == 0) 
                {
                    if (fillTempBuffer)
                    {
                        if (bytesRead > 0)
                        {
                            Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
                            Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
                        }
                    }
                    else
                    {
                        if (bytesRead > 0)
                            Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
                        Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
                    }
                    if (offset > 0)
                        throw new NotSupportedException("Reached end of stream seeking forward " + origOffset + " bytes");
                }
                fillTempBuffer = !fillTempBuffer;
            }
        }
        return Position;
    }

    private long SeekBackwards(long offset)
    {
        var intOffset = (int)offset;
        if (offset > int.MaxValue || intOffset > _seekBackBufferIndex)
            throw new NotSupportedException("Cannot currently seek backwards more than " + _seekBackBufferIndex + " bytes");
        _seekBackBufferIndex -= intOffset;
        return Position;
    }

    private long SeekFromEnd(long offset)
    {
        var intOffset = (int) offset;
        var seekBackBufferLength = _seekBackBuffer.Length;
        if (offset > int.MaxValue || intOffset > seekBackBufferLength)
            throw new NotSupportedException("Cannot seek backwards from end more than " + seekBackBufferLength + " bytes");

        // first completely fill seekBackBuffer to remove special cases from while loop below
        if (_seekBackBufferCount < seekBackBufferLength)
        {
            var maxRead = seekBackBufferLength - _seekBackBufferCount;
            var bytesRead = _underlyingStream.Read(_seekBackBuffer, _seekBackBufferCount, maxRead);
            _underlyingPosition += bytesRead;
            _seekBackBufferCount += bytesRead;
            _seekBackBufferIndex = Math.Max(0, _seekBackBufferCount - intOffset);
            if (bytesRead < maxRead)
            {
                if (_seekBackBufferCount < intOffset)
                    throw new NotSupportedException("Could not seek backwards from end " + intOffset + " bytes");
                return Position;
            }
        }
        else
        {
            _seekBackBufferIndex = _seekBackBufferCount;
        }

        // now alternate between filling tempBuffer and seekBackBuffer
        bool fillTempBuffer = true;
        var tempBuffer = new byte[seekBackBufferLength];
        while (true)
        {
            var bytesRead = _underlyingStream.Read(fillTempBuffer ? tempBuffer : _seekBackBuffer, 0, seekBackBufferLength);
            _underlyingPosition += bytesRead;
            var bytesReadDiff = seekBackBufferLength - bytesRead;
            if (bytesReadDiff > 0) // reached end-of-stream
            {
                if (fillTempBuffer)
                {
                    if (bytesRead > 0)
                    {
                        Buffer.BlockCopy(_seekBackBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
                        Buffer.BlockCopy(tempBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
                    }
                }
                else
                {
                    if (bytesRead > 0)
                        Buffer.BlockCopy(_seekBackBuffer, 0, _seekBackBuffer, bytesReadDiff, bytesRead);
                    Buffer.BlockCopy(tempBuffer, bytesRead, _seekBackBuffer, 0, bytesReadDiff);
                }
                _seekBackBufferIndex -= intOffset;
                return Position;
            }
            fillTempBuffer = !fillTempBuffer;
        }
    }

    public override long Position
    {
        get { return _underlyingPosition - (_seekBackBufferCount - _seekBackBufferIndex); }
        set { Seek(value, SeekOrigin.Begin); }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            _underlyingStream.Close();
        base.Dispose(disposing);
    }

    public override bool CanTimeout { get { return _underlyingStream.CanTimeout; } }
    public override bool CanWrite { get { return _underlyingStream.CanWrite; } }
    public override long Length { get { return _underlyingStream.Length; } }
    public override void SetLength(long value) { _underlyingStream.SetLength(value); }
    public override void Write(byte[] buffer, int offset, int count) { _underlyingStream.Write(buffer, offset, count); }
    public override void Flush() { _underlyingStream.Flush(); }
}
Hereabouts answered 20/1, 2015 at 1:29 Comment(5)
This one is missing implementation for Close and Dispose ?Lyophobic
@Lyophobic You are right. I just added Dispose(bool), which gets called by Close() and Dispose().Hereabouts
what is a seekable buffer size hereAckack
also, why do you have a .CanRead check in ReadSeekableStream constructor? Is not the whole point to convert a non-seekable to seekable... so the source will always be !.CanRead?Ackack
@Ackack seeking and reading are not the same operations, HttpRequestStream is an example of stream CanRead but not seekableLawhorn
S
3

I just use the MakeStreamSeekable method from the Amazon SDK:


MakeStreamSeekable Method (input)

Converts a non-seekable stream into a System.IO.MemoryStream. A MemoryStream's position can be moved arbitrarily.

Declaration Syntax

C#

public static Stream MakeStreamSeekable(
    Stream input
)

Parameters

*input* ([Stream][2]) The stream to be converted

Return Value

A seekable MemoryStream

Remarks

MemoryStreams use byte arrays as their backing store. Please use this judicially as it is likely that a very large stream will cause system resources to be used up.

Shamikashamma answered 27/11, 2018 at 15:12 Comment(1)
This source code of this method just copies the stream into MemoryStream in chunks, so it's essentially the same as the accepted answer.Palaeozoic
F
1

Another solution might be to create your own stream class which wraps the other stream. Implement Seek as a NOP.

class MyStream : Stream
{
    public MyStream(Stream baseStream) { this.baseStream = baseStream; }
    private Stream baseStream;

    // Delegate all operations except Seek/CanSeek to baseStream

    public override bool CanSeek { get { return true; } }
    public override long Seek(long offset, SeekOrigin origin) { return baseStream.Position; }
}

If the player is seeking for no good reason, this might just work.

Fimble answered 23/10, 2012 at 19:23 Comment(1)
Interesting idea. Unfortunately, the NAudio Mp3FileStream does a series of seek and read operations that need valid data.Warbler
F
1

Microsoft BizTalk implements a seekable stream. The concept is that it is a stream which has an input stream and a buffer stream. The input stream is the non-seekable stream and the buff stream is any stream type you want to use that can act as a buffer. So any time you seek ahead or read it copies the data to the buffer from the input stream. Any time you seek back and read something you've already read it reads from the buffer.

It's smart enough to know if the input stream is seekable and skips using the buffer if it can.

There are a number of places you can find the code. One of which is here, and I've included below. The code does depend on a VirtualStream for the buffer stream, which will use memory until the buffer is too big then use disk. You can either get the code for that as well, or easily chop out the dependency.

//---------------------------------------------------------------------
// File: SeekableReadOnlyStream.cs
// 
// Summary: A sample pipeline component which demonstrates how to promote message context
//          properties and write distinguished fields for XML messages using arbitrary
//          XPath expressions.
//
// Sample: Arbitrary XPath Property Handler Pipeline Component SDK 
//
//---------------------------------------------------------------------
// This file is part of the Microsoft BizTalk Server 2006 SDK
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// This source code is intended only as a supplement to Microsoft BizTalk
// Server 2006 release and/or on-line documentation. See these other
// materials for detailed information regarding Microsoft code samples.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------

using System;
using System.IO;
using System.Diagnostics;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp
{
    /// <summary>
    /// Implements a seekable read-only stream which uses buffering if
    /// underlying stream is not seekable. Buffer in memory has size
    /// threshold and overflows to disk (temporary file) if number of bytes.
    /// </summary>
    public class SeekableReadOnlyStream : Stream
    {
        /// <summary>
        /// Initializes a SeekableReadOnlyStream instance with base stream and 
        /// buffering stream.
        /// </summary>
        /// <param name="baseStream">Base stream</param>
        /// <param name="overflowStream">Buffering stream</param>
        public SeekableReadOnlyStream(Stream baseStream, Stream bufferingStream)
        {
            if (null == baseStream)
                throw new ArgumentNullException("baseStream");
            if (null == bufferingStream)
                throw new ArgumentNullException("bufferingStream");
            
            // Sanity check - make sure that buffering stream is seekable
            if (!bufferingStream.CanSeek)
                throw new NotSupportedException("Buffering stream must be seekable");

            this.baseStream = baseStream;
            this.bufferingStream = bufferingStream;
        }

        /// <summary>
        /// Initializes a SeekableReadOnlyStream instance with base stream and inherently uses
        /// VirtualStream instance as buffering stream.
        /// </summary>
        /// <param name="baseStream">Base stream</param>
        public SeekableReadOnlyStream(Stream baseStream) : this(baseStream, new VirtualStream())
        {
            // Empty
        }

        /// <summary>
        /// Initializes a SeekableReadOnlyStream instance with base stream and buffer size, and 
        /// inherently uses VirtualStream instance as buffering stream.
        /// </summary>
        /// <param name="baseStream">Base stream</param>
        /// <param name="bufferSize">Buffer size</param>
        public SeekableReadOnlyStream(Stream baseStream, int bufferSize) : this(baseStream, new VirtualStream(bufferSize))
        {
            // Empty
        }

        /// <summary>
        /// Gets a flag indicating whether this stream can be read.
        /// </summary>
        public override bool CanRead
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a flag indicating whether this stream can be written to.
        /// </summary>
        public override bool CanWrite
        {
            get { return false; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        /// <summary>
        /// Gets or sets a stream position.
        /// </summary>
        public override long Position
        {
            get 
            { 
                // Check if base stream is seekable
                if (baseStream.CanSeek)
                    return baseStream.Position;

                return bufferingStream.Position; 
            }
            set
            {
                // Check if base stream is seekable
                if (baseStream.CanSeek)
                {
                    baseStream.Position = value;
                    return;
                }

                // Check if current position is the same as being set
                if (bufferingStream.Position == value)
                    return;

                // Check if stream position is being set to the value which is in already
                // read to the buffering stream space, i.e. less than current buffering stream
                // position or less than length of the buffering stream
                if (value < bufferingStream.Position || value < bufferingStream.Length)
                {
                    // Just change position in the buffering stream
                    bufferingStream.Position = value;
                }
                else
                {
                    //
                    // Need to read buffer from the base stream from the current position in 
                    // base stream to the position being set and write that buffer to the end
                    // of the buffering stream
                    //

                    // Set position to the last byte in the buffering stream
                    bufferingStream.Seek(0, SeekOrigin.End);

                    // Read buffer from the base stream and write it to the buffering stream
                    // in 4K chunks
                    byte [] buffer = new byte[ 4096 ];
                    long bytesToRead = value - bufferingStream.Position;
                    while (bytesToRead > 0)
                    {
                        // Read to buffer 4K or byteToRead, whichever is less
                        int bytesRead = baseStream.Read(buffer, 0, (int) Math.Min(bytesToRead, buffer.Length));

                        // Check if any bytes were read
                        if (0 == bytesRead)
                            break;
                        
                        // Write read bytes to the buffering stream
                        bufferingStream.Write(buffer, 0, bytesRead);

                        // Decrease bytes to read counter
                        bytesToRead -= bytesRead;
                    }

                    //
                    // Since this stream is not writable, any attempt to point Position beyond the length
                    // of the base stream will not succeed, and buffering stream position will be set to the
                    // last byte in the buffering stream.
                    //
                }
            }
        }

        /// <summary>
        /// Seeks in stream. For this stream can be very expensive because entire base stream 
        /// can be dumped into buffering stream if SeekOrigin.End is used.
        /// </summary>
        /// <param name="offset">A byte offset relative to the origin parameter</param>
        /// <param name="origin">A value of type SeekOrigin indicating the reference point used to obtain the new position</param>
        /// <returns>The new position within the current stream</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            // Check if base stream is seekable
            if (baseStream.CanSeek)
                return baseStream.Seek(offset, origin);

            if (SeekOrigin.Begin == origin)
            {
                // Just set the absolute position using Position property
                Position = offset;
                return Position;
            }

            if (SeekOrigin.Current == origin)
            {
                // Set the position using current Position property value plus offset
                Position = Position + offset;
                return Position;
            }
            
            if (SeekOrigin.End == origin)
            {
                //
                // Need to read all remaining not read bytes from the base stream to the 
                // buffering stream. We can't use offset here because stream size may not
                // be available because it's not seekable. Then we'll set the position 
                // based on buffering stream size.
                //

                // Set position to the last byte in the buffering stream
                bufferingStream.Seek(0, SeekOrigin.End);

                // Read all remaining bytes from the base stream to the buffering stream
                byte [] buffer = new byte[ 4096 ];
                for (;;)
                {
                    // Read buffer from base stream
                    int bytesRead = baseStream.Read(buffer, 0, buffer.Length);
                    
                    // Break the reading loop if the base stream is exhausted
                    if (0 == bytesRead)
                        break;

                    // Write buffer to the buffering stream
                    bufferingStream.Write(buffer, 0, bytesRead);
                }

                // Now buffering stream size is equal to the base stream size. Set position
                // using begin origin
                Position = bufferingStream.Length - offset;
                return Position;
            }

            throw new NotSupportedException("Not supported SeekOrigin");
        }

        /// <summary>
        /// Gets the length in bytes of the stream. For this stream can be very expensive
        /// because entire base stream will be dumped into buffering stream.
        /// </summary>
        public override long Length
        {
            get
            {
                // Check if base stream is seekable
                if (baseStream.CanSeek)
                    return baseStream.Length;

                // Preserve the current stream position
                long position = Position;

                // Seek to the end of stream
                Seek(0, SeekOrigin.End);

                // Length will be equal to the current position
                long length = Position;

                // Restore the current stream position
                Position = position;

                return length;
            }
        }

        /// <summary>
        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
        /// </summary>
        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count- 1) replaced by the bytes read from the current source</param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream</param>
        /// <param name="count">The maximum number of bytes to be read from the current stream</param>
        /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            // Check if base stream is seekable
            if (baseStream.CanSeek)
                return baseStream.Read(buffer, offset, count);

            int bytesReadTotal = 0;

            // Check if buffering stream has some bytes to read, starting from the
            // current position
            if (bufferingStream.Length > bufferingStream.Position)
            {
                // Read available bytes in buffering stream or count bytes to the buffer, whichever is less
                bytesReadTotal = bufferingStream.Read(buffer, offset, (int) Math.Min(bufferingStream.Length - bufferingStream.Position, count));
                
                // Account for bytes read from the buffering stream
                count -= bytesReadTotal;
                offset += bytesReadTotal;
            }

            // Check if we have any more bytes to read
            if (count > 0)
            {
                Debug.Assert(bufferingStream.Position == bufferingStream.Length);

                //
                // At this point, buffering stream has position set to its end. We need to read buffer from
                // the base stream and write it to the buffering stream
                //

                // Read count bytes from the base stream starting from offset
                int bytesRead = baseStream.Read(buffer, offset, count);

                // Check if bytes were really read
                if (bytesRead > 0)
                {
                    // Write number of read bytes to the buffering stream starting from offset in buffer
                    bufferingStream.Write(buffer, offset, bytesRead);
                }

                // Add number of bytes read at this step to the number of totally read bytes
                bytesReadTotal += bytesRead;
            }

            return bytesReadTotal;
        }

        /// <summary>
        /// Writes to stream.
        /// </summary>
        /// <param name="buffer">Buffer to write to stream</param>
        /// <param name="offset">Stream offset to start write from</param>
        /// <param name="count">Number of bytes from buffer to write</param>
        /// <exception cref="NotSupportedException">Is thrown always</exception>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Set stream length.
        /// </summary>
        /// <param name="value">Stream length</param>
        /// <exception cref="NotSupportedException">Is thrown always</exception>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Closes base and buffering streams.
        /// </summary>
        public override void Close()
        {
            // Close underlying streams
            baseStream.Close();
            bufferingStream.Close();
        }

        /// <summary>
        /// Flushes the stream.
        /// </summary>
        public override void Flush()
        {
            // Flush the buffering stream
            bufferingStream.Flush();
        }


        private Stream baseStream;
        private Stream bufferingStream;
    }
}
Fart answered 30/4, 2021 at 18:19 Comment(0)
M
0

If using System.Net.WebClient, instead of using OpenRead() which returns a Stream use webClient.DownloadData("https://your.url") to get a byte array which you can then turn into a MemoryStream. Here is an example:

byte[] buffer = client.DownloadData(testBlobFile);
using (var stream = new MemoryStream(buffer))
{
    ... your code using the stream ...
}

Obviously this downloads everything before the Stream is created, so it may defeat the purpose of using a Stream.

Mullane answered 7/1, 2019 at 23:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.