Packages

Membrane Multimedia Framework (SDP protocol)

Retired package: Renamed - Renamed to ex_sdp

Current section

Files

Jump to
membrane_protocol_sdp lib sdp media.ex
Raw

lib/sdp/media.ex

defmodule Membrane.Protocol.SDP.Media do
@moduledoc """
This module represents Media field of SDP.
For more details please see [RFC4566 Section 5.14](https://tools.ietf.org/html/rfc4566#section-5.14)
"""
use Bunch
@enforce_keys [:type, :ports, :protocol, :fmt]
defstruct @enforce_keys ++
[
:title,
:encryption,
bandwidth: [],
attributes: [],
connection_data: []
]
alias Membrane.Protocol.SDP.{Attribute, Bandwidth, ConnectionData, Encryption, Session}
@typedoc """
Represents type of media. In [RFC4566](https://tools.ietf.org/html/rfc4566#section-5.14)
there are defined "audio", "video", "text", "application", and "message" types.
Known types are represented as atoms others are binaries.
"""
@type type :: :audio | :video | :text | :application | :message | binary()
@type t :: %__MODULE__{
type: type(),
ports: [:inet.port_number()],
protocol: binary(),
fmt: binary() | [0..127],
title: binary() | nil,
connection_data: [ConnectionData.sdp_address()],
bandwidth: [Bandwidth.t()],
encryption: Encryption.t() | nil,
attributes: [binary()]
}
@spec parse(binary()) :: {:ok, t()} | {:error, :invalid_media_spec | :malformed_port_number}
def parse(media) do
withl conn: [type, port, proto, fmt] <- String.split(media, " ", parts: 4),
int: {port_no, port_options} when port_no in 0..65_535 <- Integer.parse(port),
fmt: {:ok, fmt} <- parse_fmt(fmt, proto) do
media = %__MODULE__{
type: parse_type(type),
ports: gen_ports(port_no, port_options),
protocol: proto,
fmt: fmt
}
{:ok, media}
else
conn: _ -> {:error, :invalid_media_spec}
int: _ -> {:error, :malformed_port_number}
fmt: error -> error
end
end
@spec parse_optional([binary()], t()) :: {:ok, {[binary()], t()}} | {:error, atom()}
def parse_optional(lines, media)
def parse_optional([""], media), do: {:ok, {[""], finalize_optional_parsing(media)}}
def parse_optional(["m=" <> _ | _] = lines, media),
do: {:ok, {lines, finalize_optional_parsing(media)}}
def parse_optional(["i=" <> title | rest], media),
do: parse_optional(rest, %__MODULE__{media | title: title})
def parse_optional(["c=" <> conn | rest], %__MODULE__{connection_data: info} = media) do
with {:ok, conn} <- ConnectionData.parse(conn) do
conn
|> Bunch.listify()
~> %__MODULE__{media | connection_data: &1 ++ info}
~> parse_optional(rest, &1)
end
end
def parse_optional(["b=" <> bandwidth | rest], %__MODULE__{bandwidth: acc_bandwidth} = media) do
with {:ok, bandwidth} <- Bandwidth.parse(bandwidth) do
bandwidth = %__MODULE__{media | bandwidth: [bandwidth | acc_bandwidth]}
parse_optional(rest, bandwidth)
end
end
def parse_optional(["k=" <> encryption | rest], media) do
with {:ok, encryption} <- Encryption.parse(encryption) do
encryption = %__MODULE__{media | encryption: encryption}
parse_optional(rest, encryption)
end
end
def parse_optional(["a=" <> attribute | rest], %__MODULE__{attributes: attrs} = media) do
with {:ok, attribute} <- Attribute.parse(attribute),
{:ok, attribute} <- Attribute.parse_media_attribute(attribute, media.type) do
media = %__MODULE__{media | attributes: [attribute | attrs]}
parse_optional(rest, media)
end
end
@spec apply_session(__MODULE__.t(), Session.t()) :: __MODULE__.t()
def apply_session(media, session) do
session
|> Map.from_struct()
|> Enum.reduce(Map.from_struct(media), fn
{inherited_key, value}, acc
when inherited_key == :encryption ->
if acc[inherited_key] != nil,
do: acc,
else: Map.put(acc, inherited_key, value)
{inherited_key, value}, acc when inherited_key in [:connection_data, :bandwidth] ->
if acc[inherited_key] != [],
do: acc,
else: Map.put(acc, inherited_key, value)
_, acc ->
acc
end)
~> struct(__MODULE__, &1)
end
defp finalize_optional_parsing(%__MODULE__{attributes: attrs} = media) do
%__MODULE__{media | attributes: Enum.reverse(attrs)}
end
defp parse_type(type) when type in ["audio", "video", "text", "application", "message"],
do: String.to_atom(type)
defp parse_type(type) when is_binary(type), do: type
defp parse_fmt(fmt, proto) when proto == "RTP/AVP" or proto == "RTP/SAVP" do
fmt
|> String.split(" ")
|> Bunch.Enum.try_map(fn single_fmt ->
case Integer.parse(single_fmt) do
{parsed_fmt, ""} -> {:ok, parsed_fmt}
_ -> {:error, :invalid_fmt}
end
end)
end
defp parse_fmt(fmt, _), do: {:ok, fmt}
defp gen_ports(port_no, "/" <> port_count) do
port_count
|> Integer.parse()
|> case do
{port_count, ""} ->
port_no
|> Stream.unfold(fn port_no -> {port_no, port_no + 2} end)
|> Stream.take(port_count)
|> Enum.into([])
_ ->
{:error, :invalid_port_count}
end
end
defp gen_ports(port_no, _), do: [port_no]
end