【问题标题】:Elixir fetching metadata from shoutcastElixir 从直播中获取元数据
【发布时间】:2026-02-14 00:25:04
【问题描述】:

我想制作一个程序来显示当前正在播放的来自互联网广播流 (SomaFM) 的歌曲。我在 Elixir 中使用 HTTPoison 库。但我没有得到回应。它只是挂起。

我正在使用以下代码:

HTTPoison.start
url = "http://ice1.somafm.com/lush-128-mp3"
headers = [{"Icy-Metadata", "1"}]
with {:ok, %HTTPoison.Response{body: body}} <- HTTPoison.get(url, headers) do
  body |> Poison.decode! |> IO.inspect
  else
    {:error, %HTTPoison.Error{reason: reason}} ->
      IO.inspect reason  
  end
end

其实我对长生不老药很陌生,所以如果有人能帮助我,我将不胜感激。

【问题讨论】:

    标签: elixir shoutcast httpoison


    【解决方案1】:

    当您使用get 请求时,您正在请求音频文件。如果它正在流式传输,那么我想它永远不会停止“下载”。您将需要以不同的方式进行操作。

    我实际上写了一个快速示例库。你可以复制这个模块代码,因为你已经有了HTTPoison,你应该已经有了hackney作为依赖。

    示例模块:https://github.com/ryanwinchester/shoutcast_ex/blob/master/lib/shoutcast.ex

    defmodule Shoutcast do
    
      defmodule Meta do
        defstruct [:offset, :length, :data, :raw, :string]
        @type t :: %__MODULE__{
          data: map,
          offset: integer,
          length: integer,
          raw: binary,
          string: String.t
        }
      end
    
      def read_meta(url) do
        {:ok, _status, headers, ref} = :hackney.get(url, [{'Icy-Metadata', '1'}], "", [])
    
        offset = get_offset(headers)
    
        {:ok, data} = read_body(offset + 4081, ref, <<>>)
    
        {meta_length, meta} = extract_meta(data, offset)
    
        {:ok,
          %Meta{
            data: process_meta(meta),
            offset: offset,
            length: meta_length,
            raw: meta,
            string: String.trim(meta, <<0>>)
          }
        }
      end
    
      # Stream the body until we get what we want.
      defp read_body(max_length, ref, acc) when max_length > byte_size(acc) do
        case :hackney.stream_body(ref) do
          {:ok, data}      -> read_body(max_length, ref, <<acc::binary, data::binary>>)
          :done            -> {:ok, acc}
          {:error, reason} -> {:error, reason}
        end
      end
    
      defp read_body(_, _, acc), do: {:ok, acc}
    
      # Get the byte offset from the `icy-metaint` header.
      defp get_offset(headers) do
        headers
        |> Enum.into(%{})
        |> Map.get("icy-metaint")
        |> String.to_integer()
      end
    
      # Extract the meta data from the binary file stream.
      defp extract_meta(data, offset) do
        << _::binary-size(offset), length::binary-size(1), chunk::binary >> = data
    
        # The `length` byte will equal the metadata length/16.
        # Multiply by 16 to get the actual metadata length.
        <<l>> = length
        meta_length = l * 16
    
        << meta::binary-size(meta_length), _::binary >> = chunk
    
        {meta_length, meta}
      end
    
      # Process the binary meta data into a map.
      defp process_meta(meta) do
        meta
        |> String.trim_trailing(<<0>>)
        |> String.split(";")
        |> Enum.map(&String.split(&1, "="))
        |> Enum.reject(&(&1 == [""]))
        |> Enum.map(fn [k, v] -> {k, String.trim(v, "'")} end)
        |> Enum.into(%{})
      end
    end
    

    # Get meta from stream
    {:ok, meta} = Shoutcast.read_meta("http://ice1.somafm.com/lush-128-mp3")
    
    # Get title
    meta.data["StreamTitle"]
    

    我添加了Meta 结构来保存一些我觉得有趣的数据,如果您只对此感兴趣,您可以轻松删除它并修改函数以仅返回标题。

    【讨论】:

    • 实际上我试图在smackfu.com/stuff/programming/shoutcast.html 中生成这些步骤。元数据实际上在 mp3 流中。如何处理流?
    • 我会为此更新我的答案。我认为你需要流式传输块,直到你得到你想要的。
    • 感谢您的详细回答。非常感谢。