RangeFileContentResult и потоковое видео с запросами Ranged

У меня есть приложение, которое предназначено для потокового видео из нашей локальной БД. Я потратил много времени вчера, пытаясь вернуть данные a либо RangeFileContentResult, либо RangeFileStreamResult без успеха.

Короче говоря, когда я возвращаю файл как один из этих двух результатов, я не могу заставить видео правильно воспроизводить (или вообще играть).

Запрос из браузера отправляется со следующими заголовками:

Range: bytes=0-

И ответ приходит, дает эти заголовки в качестве примера:

Accept-Ranges: bytes
Content-Range: bytes 0-5103295/5103296

Что касается сетевого трафика, я получаю серию из 206 для частичных результатов, а затем 200 в конце (по мнению скрипача), которая кажется правильной. Вкладка Chrome network не согласуется с этим и видит первоначальный запрос (всегда 13 байт, которые, как я полагаю, является рукопожатием), затем еще несколько запросов, которые имеют статус либо отменены, либо ожидаются. Насколько я понимаю, это более или менее корректно, 206 - отменить, 206 - отменить и т.д. Но видео никогда не играет.

Если я переключу результат с моего контроллера на FileResult, видео воспроизводится и Chrome, IE10 и Firefox и, похоже, начнет играть до завершения загрузки (что немного похоже на потоковое воспроизведение, хотя я подозреваю, что это нет)

Но с результатом диапазона я ничего не получаю в хром или IE, а все видео загружается за один раз в firefox.

Насколько я понял, RangeFileContentResult должен обрабатывать реакцию клиента на количество байтов для загрузки (которое, похоже, не работает, оно просто сообщает ему, чтобы получить весь файл (иллюстрируется ответом выше)). И клиент должен реагировать на это, что, похоже, не делает.

Есть ли у кого-нибудь мысли в этой области? В частности:

a) Должна ли RangeFileContentResult отправлять диапазон байтов обратно клиенту? б) Есть ли способ явным образом контролировать диапазон байтов, запрошенных с клиентской стороны? c) Есть ли какая-либо причина или что-то, что я делаю неправильно здесь, что приведет к тому, что браузеры не будут загружать видео вообще при запросе RangeFileContentResult?

EDIT: добавлена ​​диаграмма, которая поможет описать, что я вижу:

RangedRequestImage

EDIT2: Хорошо, поэтому сюжет сгущается. Во время игры с губбинами RangedFile нам нужно было выпустить еще одну версию тестовой версии системы, и я оставил "RangeFileContentResult" в моем действии контроллера, как показано ниже:

    private ActionResult RetrieveVideo(MediaItem media)
    {
        return new RangeFileContentResult(media.Content, media.MimeType, media.Id.ToString(), DateTime.Now);            
    }

Скорее странно, теперь это похоже на работу в нашей тестовой среде Azure, но не на моей локальной машине. Интересно, существует ли что-то на базе IIS, которое работает на Azures IIS8, но не на моем локальном экземпляре 7.5?

Ответ 1

Причиной описанной здесь проблемы является значение, переданное параметру modificationDate конструктора RangeFileContentResult:

return new RangeFileContentResult(media.Content, media.MimeType, media.Id.ToString(), DateTime.Now); 

Эта дата используется RangeFileResult для создания двух заголовков:

  • ETag - Этот заголовок является идентификатором, используемым браузером и сервером, чтобы убедиться, что они говорят об одном и том же объекте.
  • Last-Modified - этот заголовок сообщает обозревателю о последней дате изменения сущности.

Тот факт, что a DateTime.Now передается каждый раз, когда браузер делает частичный запрос, может быть причиной изменения значений заголовков ETag и Last-Modified, прежде чем клиент получит весь объект (обычно, если весь процесс занимает больше одной секунды).

В случае, описанном выше, браузер отправляет заголовок If-Range с запросом. Этот заголовок сообщает серверу, что весь объект должен быть повторно отправлен, если тег объекта (или дата модификации, поскольку If-Range может нести одно из этих двух значений) не так много. Вот что происходит в этом случае.

Тот факт, что дата модификации является "динамической", может также вызывать дополнительные проблемы, если клиент решает использовать один из следующих заголовков для проверки: If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match.

Решение в этой ситуации состоит в том, чтобы сохранить дату изменения в базе данных с файлом, чтобы убедиться, что он согласован.

Здесь также есть место для оптимизации. Вместо того чтобы захватывать все видео из БД каждый раз, когда выполняется частичный запрос, можно либо кэшировать его, либо захватить только соответствующую часть (если механизм базы данных, используемое приложением, допускает такую ​​операцию). Такой механизм можно использовать для создания специализированного результата действия путем доставки из RangeFileResult и перезаписи методов WriteEntireEntity и WriteEntityRange.

Ответ 2

mofiПожалуйста, просто скопируйте эти два файла в проект mvc
RangeFileResult
RangeFileStreamResult

public ActionResult Movie()
{
    var path = new FileStream(@"C:\temp\01.avi", FileMode.Open);
    return new RangeFileStreamResult(path, "video/x-msvideo", "01.avi", DateTime.Now);
}

Теперь запустите проект и откройте его в chrome (например: http://youraddress.com:45454/Main/Movie) вы должны увидеть, что ваш файл воспроизводится с помощью стандартного хром-видеоплеера, это потоковая передача, и вы можете увидеть ее, если вы поставили точку останова в

return new RangeFileStreamResult(path, "video/x-msvideo", "01.avi", DateTime.Now);

Снова источник легко модифицируется для изменения размера буфера, который используется для потоковой передачи!

Ответ 3

Итак, у меня не было достаточно времени, чтобы подробно посмотреть на RangeFileResult, но я только что загрузил файл (RangeFileContentResult) из RangeFileContentResult

и изменил мой код, чтобы он выглядел как

public ActionResult Movie()
{

    byte[] file = System.IO.File.ReadAllBytes(@"C:\HOME\asp\Java\Java EE. Programming Spring 3.0\01.avi");

    return new RangeFileContentResult(file, "video/x-msvideo", "01.avi", DateTime.Now);

}

и снова он работает. Тем не менее, я заметил, что когда я останавливаю видео, у меня есть исключение, и это происходит в RangeFileResult

if (context.HttpContext.Response.IsClientConnected)
{
    WriteEntityRange(context.HttpContext.Response, RangesStartIndexes[i], RangesEndIndexes[i]);
    if (MultipartRequest)
                context.HttpContext.Response.Write("\r\n");
    context.HttpContext.Response.Flush();
}

Итак, вы лучше модифицируете код для его обработки. В терминах, когда пользователи уже отключены, но вы по-прежнему пытаетесь отправить им ответ.

Снова технически это не имеет большого значения, передаете ли вы байты [] или Stream, потому что даже когда вы передаете Stream, работающий с ним код

using (FileStream)
            {
                FileStream.Seek(rangeStartIndex, SeekOrigin.Begin);

                int bytesRemaining = Convert.ToInt32(rangeEndIndex - rangeStartIndex) + 1;
                byte[] buffer = new byte[_bufferSize];

                while (bytesRemaining > 0)
                {
                    int bytesRead = FileStream.Read(buffer, 0, _bufferSize < bytesRemaining ? _bufferSize : bytesRemaining);
                    response.OutputStream.Write(buffer, 0, bytesRead);
                    bytesRemaining -= bytesRead;
                }
            }

снова считывает данные и помещает их в массив byte []!... Итак, это вам!

НО... Я предлагаю вам обратить внимание на тип контента, который вы предоставляете!!! Дело в том, что ваш браузер должен иметь возможность справиться с этим! Поэтому, если вы предоставите что-то неизвестное, у вас возникнут проблемы. Чтобы найти строку типа контента, пожалуйста, проверьте mime-types-by-content-type

Снова я просто быстро посмотрел, и если у вас возникнут проблемы, я помогу вам позже, когда вернусь домой.