Mais acessados

segunda-feira, 18 de fevereiro de 2013

ASP.NET Web API / HTML5 - Streaming de áudio e vídeo assincronamente

Muita gente acha que a WebApi é apenas um Framework para desenvolver outros Frameworks. Mas na verdade é uma maneira de usar o máximo de desempenho e controle sobre aplicativos que utilizam HTTP. Neste breve texto irei mostrar como fazer streaming de áudio e vídeo assincronamente usando ASP.NET WebApi.

Conceito básico


Para enviarmos dados assincronamente tiraremos proveito da nova classe PushStreamContent contida no namespace System.Net.Http. Essa classe permite que possamos enviar pacotes por demanda para o cliente. A referência que tive veio do blog Andru's WebLog. Em nosso caso leremos o arquivo do disco do servidor e o enviaremos pouco a pouco para o cliente que visualizará ou escutará a mídia por meio de controles HTML5 de áudio e vídeo.

Implementação


//A classe recebe como construtor uma Action
public PushStreamContent(Action onStreamAvailable);

Criei uma classe de ajuda que lê a mídia do disco do servidor e assincronamente a salva em um buffer e libera para o cliente de acordo com a demanda. Usei as novas técnicas de programação contidas no C# 5.0 async/await para realizar a tarefa.
    public class MediaStream
    {
        private readonly string _filename;

        public MediaStream(string filename)
        {
            this._filename = filename;
        }

        public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
        {
            try
            {
                var buffer = new byte[65536];

                using (var media = File.Open(_filename, FileMode.Open, FileAccess.Read))
                {
                    var length = (int)media.Length;
                    var bytesRead = 1;

                    while (length > 0 && bytesRead > 0)
                    {
                        bytesRead = media.Read(buffer, 0, Math.Min(length, buffer.Length));
                        await outputStream.WriteAsync(buffer, 0, bytesRead);
                        length -= bytesRead;
                    }
                }
            }
            catch (HttpException ex)
            {
                return;
            }
            finally
            {
                outputStream.Close();
            }
        }
    }
Note que a assinatura do método WriteToStream corresponde a assinatura do métodos PushStreamContent. Logo depois criei duas classes, uma para Vídeo e outra para Áudio ambas filhas da classe MediaStream. Fiz dessa maneira para promover uma certa extensibilidade caso seja necessário fazer algo mais complexo mais tarde.
    public class VideoStream : MediaStream
    {
        public VideoStream(string filename) : base(filename)
        {

        }
    }
    public class AudioStream : MediaStream
    {
        public AudioStream(string filename) : base(filename)
        {

        }
    }
Agora vamos para a classe Controller para áudio e vídeo, que ficaram bastante simples após a separação nas classes anteriores.
    public class AudiosController : ApiController
    {
        // GET api/music/5
        public HttpResponseMessage Get(string filename, string ext)
        {
            filename = @"\Content\Audio\" + filename + "." + ext;

            var audio = new AudioStream(filename);

            var response = Request.CreateResponse();
            response.Content = new PushStreamContent(audio.WriteToStream, new MediaTypeHeaderValue("audio/" + ext));

            return response;
        }
    }

    public class VideosController : ApiController
    {
        // GET api/music/5
        public HttpResponseMessage Get(string filename, string ext)
        {
            filename = @"\Content\Audio\" + filename + "." + ext;

            var video = new VideoStream(filename);

            var response = Request.CreateResponse();
            response.Content = new PushStreamContent(video.WriteToStream, new MediaTypeHeaderValue("video/" + ext));

            return response;
        }
    }
O nome do arquivo deve conter o caminho completo do mesmo em seu servidor, logo quando for fazer o seu teste lembre-se disso. Agora para que o cliente possa passar um endereço e consumir os nossos serviços é necessário que este registre uma rota com a padrão correspondendo, neste exemplo estou usando api/extensão/nomeDoArquivo:
config.Routes.MapHttpRoute(
    name: "DefaultVideo",
    routeTemplate: "api/{controller}/{ext}/{filename}"
);

Consumindo os serviços


Basta criar uma página e adicionar nossos controles HTML5:
    

Música

Video

Agora você pode abrir alguma ferramenta como firebug e ver que o conteúdo está sendo transmitido por streaming e não sendo carregado como um todo. Uma observação é que você deve usar o Chrome para fazer seus testes e brincar com este exemplo, eu não consegui fazer funcionar em nenhum outro =). Espero que tenham gostado da dica, o código fonte se encontra abaixo:

2 comentários :

  1. E se em vez do cliente receber um fluxo de um vídeo armazenado no servidor eu quiser que ele se conecte a um fluxo MMS.
    No meu caso eu estou transmitindo um evento e quero que um player na página reproduza

    ResponderExcluir
  2. Funciona com vídeos grandes (100MB por exemplo)?

    ResponderExcluir