I'm currently developing a website with phoenix and have a video section that should play in the background.
Although it works fine on Chrome & Firefox, it does not work on Safari.
I suspect it is because cowboy is not serving HTTP range-request correctly.
Is there a way to enable (if disable by default) ?
$ curl -H Range:bytes=16- -I http://localhost:4000/videos/vid_home_1.mp4
HTTP/1.1 200 OK
server: Cowboy
date: Tue, 12 Apr 2016 14:41:20 GMT
content-length: 633787
cache-control: public
etag: 480A03F
content-type: video/mp4
When it should be a 206 as shown with a nginx server :
$ curl -H Range:bytes=16- -I http://localhost/videos/vid_home_1.mp4
HTTP/1.1 206 Partial Content
Server: nginx/1.8.0
Date: Tue, 12 Apr 2016 14:46:17 GMT
Content-Type: video/mp4
Content-Length: 633771
Last-Modified: Mon, 11 Apr 2016 12:26:26 GMT
Connection: keep-alive
ETag: "570b97f2-9abbb"
Content-Range: bytes 16-633786/633787
An HTTP range request asks the server to send only a portion of an HTTP message back to a client. Range requests are useful for clients like media players that support random access, data tools that know they need only part of a large file, and download managers that let the user pause and resume the download.
To initiate a partial range request, the client must initiate a request to the server and will potentially receive two headers: Accept-Ranges and Content-Length . If the server supports partial range requests, the value for Accept-Ranges will be bytes ; otherwise, the value will be none .
The Range HTTP request header indicates the part of a document that the server should return. Several parts can be requested with one Range header at once, and the server may send back these ranges in a multipart document. If the server sends back ranges, it uses the 206 Partial Content for the response.
The Accept-Ranges HTTP response header is a marker used by the server to advertise its support for partial requests from the client for file downloads. The value of this field indicates the unit that can be used to define a range.
If an HTTP response includes the Accept-Ranges header and its value is anything other than " none ", then the server supports range requests. You can perform a manual check by issuing a HEAD request with a tool like cURL.
An HTTP range request asks the server to send only a portion of an HTTP message back to a client. Range requests are useful for clients like media players that support random access, data tools that know they need only part of a large file, and download managers that let the user pause and resume the download.
There are three relevant statuses, when working with range requests: A successful range request elicits a 206 Partial Content status from the server. A range request that is out of bounds will result in a 416 Requested Range Not Satisfiable status, meaning that none of the range values overlap the extent of the resource.
The If-Range HTTP request header makes a range request conditional: if the condition is fulfilled, the range request will be issued and the server sends back a 206 Partial Content answer with the appropriate body. If the condition is not fulfilled, the full resource is sent back, with a 200 OK status.
I found a way to do it myself with Plugs... So if anyone want to serve Range Request with Phoenix / Elixir Here's what you have to do (This is pretty basic and does not take into account rfc)
defmodule Plug.Range do
@behaviour Plug
@allowed_methods ~w(GET HEAD)
import Plug.Conn
def init(options) do
options
end
def call(conn, _opts) do
if (Enum.empty?(Plug.Conn.get_req_header(conn, "range"))) do
conn
else
file_path = "priv/static" <> conn.request_path
if File.exists? file_path do
stats = File.stat! file_path
filesize = stats.size
req = Regex.run(~r/bytes=([0-9]+)-([0-9]+)?/, conn |> Plug.Conn.get_req_header("range") |> List.first)
{req_start, _} = req |> Enum.at(1) |> Integer.parse
{req_end, _} = req |> Enum.at(2, filesize |> to_string) |> Integer.parse
file_end = ( filesize - 2) |> to_string
length = req_end - req_start + 1
conn
|> Plug.Conn.put_resp_header("Content-Type", "video/mp4")
|> Plug.Conn.put_resp_header("Accept-Ranges", "bytes")
|> Plug.Conn.put_resp_header("Content-Range", "bytes #{req_start}-#{req_end}/#{filesize}")
|> Plug.Conn.send_file(206, file_path, req_start, length)
|> Plug.Conn.halt
else
conn
end
end
end
end
As you can see, right now, it will only send "video/mp4" content-Type, but you can easily make something work for everything...
Finally for the Plug to work, you need to place it just before Plug.static in your Project Endpoint file.
Hope it helps someone...
EDIT :
For those who are interested, I have created a github/hex.pm package for this:
Hex link
github link
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With