Search code examples
streamingelixirhtml5-audioaudio-streaming

Elixir stream audio to users


The following code streams a file to a process. I want to stream audio/mp3 to many users who will hear it via html5 audio tag. How can it be done via File.stream!?

defmodule Test do

    def start do
        p = spawn(Test, :say, [])
        send p, {self, "a message"}
    end

    def say do
        receive do
          {from, msg} ->
            IO.puts "Process #{inspect self} says: #{msg}"
            stream_bytes = 128
            File.stream!("./song.mp3", [], stream_bytes)
            |> Enum.each(fn chunk ->
                IO.inspect chunk
            end)
            say
        end
    end
end

$: iex test.ex

iex(1)> Test.start

output:

<<171, 46, 254, 26, 163, 32, 178, 27, 0, 75, 17, 35, 4, 236, 51, 57, 5, 144, 154, 198, 166, 47, 62, 4, 61, 85, 67, 135, 16, 34, 82, 49, 57, 176, 131, 96, 116, 152, 232, 24, 32, 140, 220, 67, 73, 128, 165, 178, 230, 202, ...>> <<100, 220, 156, 191, 38, 0, 161, 117, 80, 16, 102, 91, 22, 5, 8, 66, 26, 7, 193, 155, 193, 66, 198, 28, 157, 244, 65, 131, 204, 240, 5, 172, 143, 44, 173, 85, 144, 2, 157, 144, 145, 97, 200, 236, 16, 49, 149, 150, 133, 67, ...>> <<150, 54, 37, 254, 192, 218, 218, 26, 69, 231, 88, 124, 33, 129, 169, 66, 117, 52, 214, 134, 130, 103, 85, 130, 48, 6, 144, 221, 153, 132, 8, 181, 26, 27, 83, 140, 54, 117, 149, 7, 60, 144, 237, 248, 132, 12, 210, 51, 103, 116, ...>> <<57, 2, 143, 220, 198, 182, 22, 177, 231, 126, 187, 147, 33, 9, 1, 5, 164, 2, 36, 105, 47, 255, 255, 255, 255, 255, 245, 54, 51, 225, 104, 98, 1, 184, 148, 206, 50, 135, 230, 28, 50, 47, 144, 134, 53, 16, 64, 130, 192, 198, ...>> ..............

how can I use JavaScript to read this binary data and hear it via audio tag ?


Solution

  • If you're using a plug based web framework it should be reasonably straight forward. This is possible if you're using plug directly or if you're using it from within phoenix (which is based on plug).

    Maybe a plug like this would do the trick

    defmodule Audio do
      @chunk_size 128
    
      def init(opts), do: opts
    
      def song(conn, _opts) do
        conn = conn
        |> send_chunked(200)
        |> put_resp_header("content-type", "audio/mpeg")
    
        File.stream!("/some/song/somewhere.mp3", [], @chunk_size)
        |> Enum.into(conn)
      end 
    end
    

    Maybe you want to hook up your plug to a phoenix router like this

    defmodule MyApp.Router do
      use MyApp.Web, :router
    
      get "/the_song", Audio, :song
    end
    

    Then in your page

    <audio src="/the_song">
        Your browser does not support the <code>audio</code> element.
    </audio>