Rails媒体文件流通过send_data或send_file方法接受字节范围请求

发布于 2024-11-25 08:29:58 字数 276 浏览 4 评论 0原文

我有以下问题。声音在公共文件夹中隐藏,因为只有某些用户有权访问声音文件。所以我做了一个特定的方法,它的作用就像一个声音url,但首先计算当前用户是否允许访问这个文件。

文件通过 send_data 方法发送。问题是,如果它甚至可以工作的话,它的工作速度会非常慢...我用来播放声音的 jplayer 插件的开发人员告诉我,我应该能够接受字节范围请求以使其正常工作...

如何通过使用 send_data 或 send_file 发送文件来在 Rails 控制器中执行此操作?

谢谢, 马库斯

I have the following problem. Sounds are hidden from the public folder, cause there are only certain Users who should have access to the sound files. So I made a certain method, which acts like a sound url, but calculates first, whether the current user is allowed to access this file.

The file gets sent by the send_data method. The problem is just, that I it works quite slow if it works even... The developer of the jplayer plugin, which I use to play the sound, told me that I should be able to accept byte range requests to make it work properly...

How can I do this within a rails controller by sending the file with send_data or send_file?

Thanks,
Markus

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

满身野味 2024-12-02 08:29:58

我已经能够使用 send_file 成功地提供文件。尽管我遇到了一个问题,但搜索歌曲的较早部分会导致新的请求,从而使歌曲从 0:00 而不是搜索栏的真实位置重新开始。到目前为止,这就是我为我所做的工作:

  file_begin = 0
  file_size = @media.file_file_size 
  file_end = file_size - 1

  if !request.headers["Range"]
    status_code = "200 OK"
  else
    status_code = "206 Partial Content"
    match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)
    if match
      file_begin = match[1]
      file_end = match[1] if match[2] && !match[2].empty?
    end
    response.header["Content-Range"] = "bytes " + file_begin.to_s + "-" + file_end.to_s + "/" + file_size.to_s
  end
  response.header["Content-Length"] = (file_end.to_i - file_begin.to_i + 1).to_s
  response.header["Last-Modified"] = @media.file_updated_at.to_s

  response.header["Cache-Control"] = "public, must-revalidate, max-age=0"
  response.header["Pragma"] = "no-cache"
  response.header["Accept-Ranges"]=  "bytes"
  response.header["Content-Transfer-Encoding"] = "binary"
  send_file(DataAccess.getUserMusicDirectory(current_user.public_token) + @media.sub_path, 
            :filename => @media.file_file_name,
            :type => @media.file_content_type, 
            :disposition => "inline",
            :status => status_code,
            :stream =>  'true',
            :buffer_size  =>  4096)

I've been able to serve up the files with some success using send_file. Although I have one hitch, seeking to an earlier part of the song causes a new request which makes the song restart from 0:00 instead of the true location from the seekbar. This is what I have working for me so far:

  file_begin = 0
  file_size = @media.file_file_size 
  file_end = file_size - 1

  if !request.headers["Range"]
    status_code = "200 OK"
  else
    status_code = "206 Partial Content"
    match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)
    if match
      file_begin = match[1]
      file_end = match[1] if match[2] && !match[2].empty?
    end
    response.header["Content-Range"] = "bytes " + file_begin.to_s + "-" + file_end.to_s + "/" + file_size.to_s
  end
  response.header["Content-Length"] = (file_end.to_i - file_begin.to_i + 1).to_s
  response.header["Last-Modified"] = @media.file_updated_at.to_s

  response.header["Cache-Control"] = "public, must-revalidate, max-age=0"
  response.header["Pragma"] = "no-cache"
  response.header["Accept-Ranges"]=  "bytes"
  response.header["Content-Transfer-Encoding"] = "binary"
  send_file(DataAccess.getUserMusicDirectory(current_user.public_token) + @media.sub_path, 
            :filename => @media.file_file_name,
            :type => @media.file_content_type, 
            :disposition => "inline",
            :status => status_code,
            :stream =>  'true',
            :buffer_size  =>  4096)
情归归情 2024-12-02 08:29:58

我使用了加勒特的答案并对其进行了修改(包括一两个错误修复)。我还使用 send_data 而不是从文件中读取:

  def stream_data data, options={}
    range_start = 0
    file_size = data.length
    range_end = file_size - 1
    status_code = "200"

    if request.headers["Range"]
      status_code = "206"
      request.headers['range'].match(/bytes=(\d+)-(\d*)/).try do |match|
        range_start = match[1].to_i
        range_end = match[2].to_i unless match[2]&.empty?
      end
      response.header["Content-Range"] = "bytes #{range_start}-#{range_end}/#{file_size}"
    end

    response.header["Content-Length"] = (range_end - range_start + 1).to_s
    response.header["Accept-Ranges"] = "bytes"

    send_data(data[range_start, range_end],
              filename: options[:filename],
              type: options[:type],
              disposition: "inline",
              status: status_code)
  end

I used Garrett's answer and modified it (including one or two bug fixes). I also used send_data instead of reading from a file:

  def stream_data data, options={}
    range_start = 0
    file_size = data.length
    range_end = file_size - 1
    status_code = "200"

    if request.headers["Range"]
      status_code = "206"
      request.headers['range'].match(/bytes=(\d+)-(\d*)/).try do |match|
        range_start = match[1].to_i
        range_end = match[2].to_i unless match[2]&.empty?
      end
      response.header["Content-Range"] = "bytes #{range_start}-#{range_end}/#{file_size}"
    end

    response.header["Content-Length"] = (range_end - range_start + 1).to_s
    response.header["Accept-Ranges"] = "bytes"

    send_data(data[range_start, range_end],
              filename: options[:filename],
              type: options[:type],
              disposition: "inline",
              status: status_code)
  end
奶气 2024-12-02 08:29:58

这是我的版本。我使用 gem 'ogginfo-rb' 来计算正确提供 ogg 文件所需的持续时间。
ps 我总是有三种格式 - wav、mp3、ogg。

the_file = File.open(file_path)

file_begin = 0
file_size = the_file.size
file_end = file_size - 1

if request.headers['Range']
  status_code = :partial_content

  match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)

  if match
    file_begin = match[1]
    file_end = match[1]  if match[2] and not match[2].empty?
  end

  response.headers['Content-Range'] = "bytes #{file_begin}-#{file_end.to_i + (match[2] == '1' ? 1 : 0)}/#{file_size}"
else
  status_code = :ok
end

response.headers['Content-Length'] = (file_end.to_i - file_begin.to_i + 1).to_s
response.headers['Last-Modified'] = the_file.mtime

response.headers['Cache-Control'] = 'public, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Accept-Ranges'] = 'bytes'
response.headers['Content-Transfer-Encoding'] = 'binary'

require 'ogginfo-rb'
ogginfo = Ogg::Info::open(the_file.path.gsub(/.mp3|.wav/,'.ogg'))
duration = ogginfo.duration.to_f

response.headers['Content-Duration'] = duration
response.headers['X-Content-Duration'] = duration

send_file file_path,
          filename: "#{call.id}.#{ext}",
          type: Mime::Type.lookup_by_extension(ext),
          status: status_code,
          disposition: 'inline',
          stream: 'true',
          buffer_size: 32768

Here is my version. I use gem 'ogginfo-rb' to calculate the duration which is required to serve ogg files properly.
p.s. I always have three formats - wav, mp3, ogg.

the_file = File.open(file_path)

file_begin = 0
file_size = the_file.size
file_end = file_size - 1

if request.headers['Range']
  status_code = :partial_content

  match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)

  if match
    file_begin = match[1]
    file_end = match[1]  if match[2] and not match[2].empty?
  end

  response.headers['Content-Range'] = "bytes #{file_begin}-#{file_end.to_i + (match[2] == '1' ? 1 : 0)}/#{file_size}"
else
  status_code = :ok
end

response.headers['Content-Length'] = (file_end.to_i - file_begin.to_i + 1).to_s
response.headers['Last-Modified'] = the_file.mtime

response.headers['Cache-Control'] = 'public, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Accept-Ranges'] = 'bytes'
response.headers['Content-Transfer-Encoding'] = 'binary'

require 'ogginfo-rb'
ogginfo = Ogg::Info::open(the_file.path.gsub(/.mp3|.wav/,'.ogg'))
duration = ogginfo.duration.to_f

response.headers['Content-Duration'] = duration
response.headers['X-Content-Duration'] = duration

send_file file_path,
          filename: "#{call.id}.#{ext}",
          type: Mime::Type.lookup_by_extension(ext),
          status: status_code,
          disposition: 'inline',
          stream: 'true',
          buffer_size: 32768
风苍溪 2024-12-02 08:29:58

另一个修改版本 - 我试图下载一个 zip 文件作为二进制内容,这对我有用 -

  def byte_range_response (request, response, content)
    file_begin = 0
    file_size = content.bytesize
    file_end = file_size - 1

    status_code = '206 Partial Content'
    match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)
    if match
      file_begin = match[1]
      file_end = match[2] if match[2] && !match[2].empty?
    end
    content_length = file_end.to_i - file_begin.to_i + 1
    response.header['Content-Range'] = 'bytes ' + file_begin.to_s + '-' + file_end.to_s + '/' + file_size.to_s
    response.header['Content-Length'] = content_length.to_s
    response.header['Cache-Control'] = 'public, must-revalidate, max-age=0'
    response.header['Pragma'] = 'no-cache'
    response.header['Accept-Ranges']= 'bytes'
    response.header['Content-Transfer-Encoding'] = 'binary'
    send_data get_partial_content(content, content_length, file_begin.to_i), type: 'application/octet-stream', status: status_code
  end

  def get_partial_content(content, content_length, offset)
    test_file = Tempfile.new(['test-file', '.zip'])
    test_file.puts(content)
    partial_content = IO.binread(test_file.path, content_length, offset)
    test_file.close
    test_file.unlink
    partial_content
  end

Another amended version - I was trying to download a zip file as binary content and this is what worked for me -

  def byte_range_response (request, response, content)
    file_begin = 0
    file_size = content.bytesize
    file_end = file_size - 1

    status_code = '206 Partial Content'
    match = request.headers['range'].match(/bytes=(\d+)-(\d*)/)
    if match
      file_begin = match[1]
      file_end = match[2] if match[2] && !match[2].empty?
    end
    content_length = file_end.to_i - file_begin.to_i + 1
    response.header['Content-Range'] = 'bytes ' + file_begin.to_s + '-' + file_end.to_s + '/' + file_size.to_s
    response.header['Content-Length'] = content_length.to_s
    response.header['Cache-Control'] = 'public, must-revalidate, max-age=0'
    response.header['Pragma'] = 'no-cache'
    response.header['Accept-Ranges']= 'bytes'
    response.header['Content-Transfer-Encoding'] = 'binary'
    send_data get_partial_content(content, content_length, file_begin.to_i), type: 'application/octet-stream', status: status_code
  end

  def get_partial_content(content, content_length, offset)
    test_file = Tempfile.new(['test-file', '.zip'])
    test_file.puts(content)
    partial_content = IO.binread(test_file.path, content_length, offset)
    test_file.close
    test_file.unlink
    partial_content
  end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文