How can I implement the flow in the first answer here on Phoenix? At first I thought I had to use Conn.send_chunked
and Conn.chunk
but I am starting to realize I will be getting many requests instead of me sending a various responses.
Asked
Active
Viewed 462 times
2

Community
- 1
- 1

Cristian Garcia
- 9,630
- 6
- 54
- 75
3 Answers
2
If anybody is interested, this is my solution. Talks correctly to video players, and also handles normal files.
[range_start, range_end] =
if Enum.empty?(Conn.get_req_header(conn, "range")) do
[0, file.content_length - 1]
else
[rn] = Conn.get_req_header(conn, "range")
res = Regex.run(~r/bytes=([0-9]+)-([0-9])?/, rn)
default_end = Integer.to_string(file.content_length - 1)
{range_start, _} = res |> Enum.at(1) |> Integer.parse
{range_end, _} = res |> Enum.at(2, default_end) |> Integer.parse
[range_start, range_end]
end
content_length = range_end - range_start + 1
conn = conn
|> Conn.put_resp_content_type(file.content_type)
|> Conn.put_resp_header("content-length", Integer.to_string(content_length))
|> Conn.put_resp_header("accept-ranges", "bytes")
|> Conn.put_resp_header("content-disposition", ~s(inline; filename="#{file.filename}"))
|> Conn.put_resp_header("content-range", "bytes #{range_start}-#{range_end}/#{file.content_length}")
|> Conn.send_file(206, "#{files_path}/#{id}", range_start, content_length)

Cristian Garcia
- 9,630
- 6
- 54
- 75
1
Thanks for posting your solution Cristian! I never would've figured this out otherwise...
For any Elixir newbies such as myself who run across this and can't quite get the above snippet to work, here's my full controller method:
def load_song(conn, params) do
filename = List.first(params["song"])
file_path = "priv/static/sounds/#{filename}"
{:ok, stats} = File.stat(file_path)
filesize = stats.size
[range_start, range_end] =
if Enum.empty?(Plug.Conn.get_req_header(conn, "range")) do
[0, filesize - 1]
else
[rn] = Plug.Conn.get_req_header(conn, "range")
res = Regex.run(~r/bytes=([0-9]+)-([0-9])?/, rn)
default_end = Integer.to_string(filesize - 2)
{range_start, _} = res |> Enum.at(1) |> Integer.parse
{range_end, _} = res |> Enum.at(2, default_end) |> Integer.parse
[range_start, range_end]
end
content_length = range_end - range_start + 2
conn
|> Plug.Conn.put_resp_content_type("audio/mpeg")
|> Plug.Conn.put_resp_header("content-length", Integer.to_string(content_length))
|> Plug.Conn.put_resp_header("accept-ranges", "bytes")
|> Plug.Conn.put_resp_header("content-disposition", ~s(inline; filename="#{filename}"))
|> Plug.Conn.put_resp_header("content-range", "bytes #{range_start}-#{range_end}/#{filesize}")
|> Plug.Conn.send_file(206, file_path, range_start, content_length)
end
I've learned since about some basic Elixir features like aliasing that would've simplified this, and I'm guessing some other coding choices are a little un-idiomatic, but the above show work as a "drop in" method for your controller.

Wikk
- 63
- 1
- 10
-
Glad it helped, I spend a whole afternoon figuring it out, I hope other can get it done quicker with the answer. I see you changed the math a little. – Cristian Garcia Dec 04 '15 at 19:39
-
Oops, I may have done that accidentally. I'm used to working at higher levels of abstraction than raw bytes, so I was a little uncertain figuring out content length and file size :) I'll have to do some experiments this afternoon. – Wikk Dec 05 '15 at 16:46