lib/ex_sdp/attribute/simulcast.ex

defmodule ExSDP.Attribute.Simulcast do
  @moduledoc """
  This module represents simulcast (RFC 8853).
  """

  defstruct send: [], recv: []

  @type rid() :: String.t()
  @type t :: %__MODULE__{
          send: [rid() | [rid()]],
          recv: [rid() | [rid()]]
        }

  @typedoc """
  Key that can be used for searching this attribute using `ExSDP.Media.get_attribute/2`.
  """
  @type attr_key :: :simulcast

  @spec parse(binary()) :: {:ok, t()} | {:error, :invalid_simulcast}
  def parse(simulcast) do
    case String.split(simulcast, " ") do
      ["send", send] -> {:ok, "", send}
      ["recv", recv] -> {:ok, recv, ""}
      ["recv", recv, "send", send] -> {:ok, recv, send}
      ["send", send, "recv", recv] -> {:ok, recv, send}
      _other -> {:error, :invalid_simulcast}
    end
    |> case do
      {:ok, recv, send} ->
        send = parse_streams(send)
        recv = parse_streams(recv)
        {:ok, %__MODULE__{send: send, recv: recv}}

      {:error, _res} = err ->
        err
    end
  end

  defp parse_streams(""), do: []

  defp parse_streams(streams) do
    streams
    |> String.split(";")
    |> Enum.map(&String.split(&1, ","))
    |> Enum.map(fn
      [rid] -> rid
      rids -> rids
    end)
  end
end

defimpl String.Chars, for: ExSDP.Attribute.Simulcast do
  alias ExSDP.Attribute.Simulcast

  @impl true
  def to_string(simulcast) do
    %Simulcast{send: send, recv: recv} = simulcast
    send = encode_streams(send)
    send = if(send == "", do: [], else: ["send", send])
    recv = encode_streams(recv)
    recv = if(recv == "", do: [], else: ["recv", recv])
    send_recv = Enum.join(send ++ recv, " ")

    "simulcast:#{send_recv}"
  end

  defp encode_streams(streams) do
    Enum.map_join(streams, ";", fn
      rids when is_list(rids) -> Enum.join(rids, ",")
      rid -> rid
    end)
  end
end