Comment diffuser une vidéo ou un fichier en tenant compte des en-têtes request et response range?

j'utilise maintenant FileStreamResult et cela fonctionne pour diffuser une vidéo, mais ne peut pas la chercher. Ça recommence toujours du début.

j'utilisais ByteRangeStreamContent mais il semble qu'elle n'est plus disponible avec dnxcore50.

alors comment procéder ?

Est-ce que je dois analyser manuellement les en-têtes des champs de requête et écrire une commandeFileResult qui définit la réponse Content-Range et le reste des en-têtes et écrit la zone tampon au corps de réponse ou y a-t-il quelque chose déjà mis en œuvre et que je suis absent ?

12
demandé sur Cristi Pufu 2015-12-02 18:59:57

2 réponses

Voici une implémentation naïve d'un VideoStreamResult, j'utilise pour le moment (la partie multipart content n'est pas testée):

public class VideoStreamResult : FileStreamResult
{
    // default buffer size as defined in BufferedStream type
    private const int BufferSize = 0x1000;
    private string MultipartBoundary = "<qwe123>";

    public VideoStreamResult(Stream fileStream, string contentType)
        : base(fileStream, contentType)
    {

    }

    public VideoStreamResult(Stream fileStream, MediaTypeHeaderValue contentType) 
        : base(fileStream, contentType)
    {

    }

    private bool IsMultipartRequest(RangeHeaderValue range)
    {
        return range != null && range.Ranges != null && range.Ranges.Count > 1;
    }

    private bool IsRangeRequest(RangeHeaderValue range)
    {
        return range != null && range.Ranges != null && range.Ranges.Count > 0;
    }

    protected async Task WriteVideoAsync(HttpResponse response)
    {
        var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
        bufferingFeature?.DisableResponseBuffering();

        var length = FileStream.Length;

        var range = response.HttpContext.GetRanges(length);

        if (IsMultipartRequest(range))
        {
            response.ContentType = $"multipart/byteranges; boundary={MultipartBoundary}";
        }
        else
        {
            response.ContentType = ContentType.ToString();
        }

        response.Headers.Add("Accept-Ranges", "bytes");

        if (IsRangeRequest(range))
        {
            response.StatusCode = (int)HttpStatusCode.PartialContent;

            if (!IsMultipartRequest(range))
            {
                response.Headers.Add("Content-Range", $"bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
            }

            foreach (var rangeValue in range.Ranges)
            {
                if (IsMultipartRequest(range)) // dunno if multipart works
                {
                    await response.WriteAsync($"--{MultipartBoundary}");
                    await response.WriteAsync(Environment.NewLine);
                    await response.WriteAsync($"Content-type: {ContentType}");
                    await response.WriteAsync(Environment.NewLine);
                    await response.WriteAsync($"Content-Range: bytes {range.Ranges.First().From}-{range.Ranges.First().To}/{length}");
                    await response.WriteAsync(Environment.NewLine);
                }

                await WriteDataToResponseBody(rangeValue, response);

                if (IsMultipartRequest(range))
                {
                    await response.WriteAsync(Environment.NewLine);
                }
            }

            if (IsMultipartRequest(range))
            {
                await response.WriteAsync($"--{MultipartBoundary}--");
                await response.WriteAsync(Environment.NewLine);
            }
        }
        else
        {
            await FileStream.CopyToAsync(response.Body);
        }
    }

    private async Task WriteDataToResponseBody(RangeItemHeaderValue rangeValue, HttpResponse response)
    {
        var startIndex = rangeValue.From ?? 0;
        var endIndex = rangeValue.To ?? 0;

        byte[] buffer = new byte[BufferSize];
        long totalToSend = endIndex - startIndex;
        int count = 0;

        long bytesRemaining = totalToSend + 1;
        response.ContentLength = bytesRemaining;

        FileStream.Seek(startIndex, SeekOrigin.Begin);

        while (bytesRemaining > 0)
        {
            try
            {
                if (bytesRemaining <= buffer.Length)
                    count = FileStream.Read(buffer, 0, (int)bytesRemaining);
                else
                    count = FileStream.Read(buffer, 0, buffer.Length);

                if (count == 0)
                    return;

                await response.Body.WriteAsync(buffer, 0, count);

                bytesRemaining -= count;
            }
            catch (IndexOutOfRangeException)
            {
                await response.Body.FlushAsync();
                return;
            }
            finally
            {
                await response.Body.FlushAsync();
            }
        }
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        await WriteVideoAsync(context.HttpContext.Response);
    }
}

Et analyser les en-têtes de requête plage:

public static RangeHeaderValue GetRanges(this HttpContext context, long contentSize)
        {
            RangeHeaderValue rangesResult = null;

            string rangeHeader = context.Request.Headers["Range"];

            if (!string.IsNullOrEmpty(rangeHeader))
            {
                // rangeHeader contains the value of the Range HTTP Header and can have values like:
                //      Range: bytes=0-1            * Get bytes 0 and 1, inclusive
                //      Range: bytes=0-500          * Get bytes 0 to 500 (the first 501 bytes), inclusive
                //      Range: bytes=400-1000       * Get bytes 500 to 1000 (501 bytes in total), inclusive
                //      Range: bytes=-200           * Get the last 200 bytes
                //      Range: bytes=500-           * Get all bytes from byte 500 to the end
                //
                // Can also have multiple ranges delimited by commas, as in:
                //      Range: bytes=0-500,600-1000 * Get bytes 0-500 (the first 501 bytes), inclusive plus bytes 600-1000 (401 bytes) inclusive

                // Remove "Ranges" and break up the ranges
                string[] ranges = rangeHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());

                rangesResult = new RangeHeaderValue();

                for (int i = 0; i < ranges.Length; i++)
                {
                    const int START = 0, END = 1;

                    long endByte, startByte;

                    long parsedValue;

                    string[] currentRange = ranges[i].Split("-".ToCharArray());

                    if (long.TryParse(currentRange[END], out parsedValue))
                        endByte = parsedValue;
                    else
                        endByte = contentSize - 1;


                    if (long.TryParse(currentRange[START], out parsedValue))
                        startByte = parsedValue;
                    else
                    {
                        // No beginning specified, get last n bytes of file
                        // We already parsed end, so subtract from total and
                        // make end the actual size of the file
                        startByte = contentSize - endByte;
                        endByte = contentSize - 1;
                    }

                    rangesResult.Ranges.Add(new RangeItemHeaderValue(startByte, endByte));
                }
            }

            return rangesResult;
        }
18
répondu Cristi Pufu 2016-08-05 16:12:20

pour votre information, le support intégré pour les requêtes de portée sera présent dans le noyau .net 2.1

https://github.com/aspnet/Mvc/pull/6895

4
répondu Roger 2017-10-27 17:31:06