I have a Rails app that protects the uploaded videos putting them into a private folder.
Now I need to play these videos, and when I do something like this in the controller:
def show video = Video.find(params[:id]) send_file(video.full_path, type: "video/mp4", disposition: "inline") end
And open the browser(Chrome or FF) at /videos/:id it doesn't play the video.
If I put the same video at the public folder, and access it like /video.mp4 it will play.
If I remove the dispositon: "inline" it will download the video and I can play it from my computer. Samething happens with webm videos.
What am I missing? Is this something possible to do?
To stream videos, we have to handle the requested byte range for some browsers.
Solution 1: Use the send_file_with_range
gem
The easy way would be to have the send_file
method patched by the send_file_with_range gem.
Include the gem in the Gemfile
# Gemfile gem 'send_file_with_range'
and provide the range: true
option for send_file
:
def show video = Video.find(params[:id]) send_file video.full_path, type: "video/mp4", disposition: "inline", range: true end
The patch is quite short and worth a look. But, unfortunately, it did not work for me with Rails 4.2.
Solution 2: Patch send_file
manually
Inspired by the gem, extending the controller manually is fairly easy:
class VideosController < ApplicationController def show video = Video.find(params[:id]) send_file video.full_path, type: "video/mp4", disposition: "inline", range: true end private def send_file(path, options = {}) if options[:range] send_file_with_range(path, options) else super(path, options) end end def send_file_with_range(path, options = {}) if File.exist?(path) size = File.size(path) if !request.headers["Range"] status_code = 200 # 200 OK offset = 0 length = File.size(path) else status_code = 206 # 206 Partial Content bytes = Rack::Utils.byte_ranges(request.headers, size)[0] offset = bytes.begin length = bytes.end - bytes.begin end response.header["Accept-Ranges"] = "bytes" response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes send_data IO.binread(path, length, offset), options else raise ActionController::MissingFile, "Cannot read file #{path}." end end end
Further reading
Because, at first, I did not know the difference between stream: true
and range: true
, I found this railscast helpful:
http://railscasts.com/episodes/266-http-streaming