RangeFileContentResult и потоковое видео с удаленными запросами

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

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

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

Range: bytes=0-

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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: Добавлена ​​схема, чтобы помочь описать то, что я вижу:

RangedRequestImage

РЕДАКТИРОВАТЬ 2: Хорошо, так что сюжет утолщается. Пока мы играли с RubedFile gubbins, нам нужно было выдвинуть другую тестовую версию системы, и я оставил "RangeFileContentResult" в своем действии контроллера, как показано ниже:

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

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

3 ответа

Решение

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

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

Эта дата используется RangeFileResult чтобы создать два заголовка:

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

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

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

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

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

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

Итак, у меня не было достаточно времени, чтобы посмотреть на 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();
}

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

Опять же, технически это не большая разница, передаете ли вы byte[] или 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

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

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), вы должны увидеть, как ваш файл воспроизводится с помощью стандартного видеопроигрывателя Chrome. он потоковый, и вы можете увидеть его, если поставить точку останова на

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

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

Другие вопросы по тегам