Streaming mp4 in Chrome with rails, nginx and send_file

后端 未结 1 656
夕颜
夕颜 2020-12-30 09:14

I can\'t for the life of me stream a mp4 to Chrome with a html5 tag. If I drop the file in public then everything is gravy and works

相关标签:
1条回答
  • 2020-12-30 09:47

    I finally have the answers to my original questions. I didn't think I'd ever get here. All my research had lead to dead-ends, hacky non-solutions and "it just works out of the box" (well, not for me).

    Why doesn't nginx/apache handle all this stuff automagically with send_file (X-Accel-Redirect/X-Sendfile) like it does when the file is served statically from public? Handling this stuff in rails is so backwards.

    They do, but they have to be configured properly to please Rack::Sendfile (see below). Trying to handle this in rails is a hacky non-solution.

    How the heck can I actually use send_file with nginx (or apache) so that Chrome will be happy and allow seeking?

    I got desperate enough to start poking around rack source code and that's where I found my answer, in the comments of Rack::Sendfile. They are structured as documentation that you can find at rubydoc.

    For whatever reason, Rack::Sendfile requires the front end proxy to send a X-Sendfile-Type header. In the case of nginx it also requires a X-Accel-Mapping header. The documentation also has examples for apache and lighttpd as well.

    One would think the rails documentation could link to the Rack::Sendfile documentation since send_file does not work out of the box without additional configuration. Perhaps I'll submit a pull request.

    In the end I only needed to add a couple lines to my app.conf:

    upstream unicorn {
      server unix:/tmp/unicorn.app.sock fail_timeout=0;
    }
    
    server {
      listen 80 default deferred;
      root /vagrant/public;
      try_files $uri/index.html $uri @unicorn;
      location @unicorn {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header HOST $http_host;
        proxy_set_header X-Sendfile-Type X-Accel-Redirect; # ADDITION
        proxy_set_header X-Accel-Mapping /=/; # ADDITION
        proxy_redirect off;
        proxy_pass http://localhost:3000;
      }
    
      error_page 500 502 503 504 /500.html;
      client_max_body_size 4G;
      keepalive_timeout 5;
    }
    

    Now my original code works as expected:

    def show
      send_file(Video.find(params[:id]).location)
    end
    

    Edit:

    Although this worked initially, it stopped working after I restarted my vagrant box and I had to make further changes:

    upstream unicorn {
      server unix:/tmp/unicorn.app.sock fail_timeout=0;
    }
    
    server {
      listen 80 default deferred;
      root /vagrant/public;
      try_files $uri/index.html $uri @unicorn;
    
      location ~ /files(.*) { # NEW
        internal;             # NEW
        alias $1;             # NEW
      }                       # NEW
    
      location @unicorn {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header HOST $http_host;
        proxy_set_header X-Sendfile-Type X-Accel-Redirect;
        proxy_set_header X-Accel-Mapping /=/files/; # CHANGED
        proxy_redirect off;
        proxy_pass http://localhost:3000;
      }
    
      error_page 500 502 503 504 /500.html;
      client_max_body_size 4G;
      keepalive_timeout 5;
    }
    

    I find this whole thing of mapping one URI to another and then mapping that URI to a location on disk to be totally unnecessary. It's useless for my use case and I'm just mapping one to another and back again. Apache and lighttpd don't require it. But at least it works.

    I also added Mime::Type.register('video/mp4', :mp4) to config/initializers/mime_types.rb so the file is served with the correct mime type.

    0 讨论(0)
提交回复
热议问题