View Source Specter (specter v0.2.1)

Specter is a method for managing data structures and entities provided by webrtc.rs. It is intended as a low-level library with some small set of opinions, which can composed into more complex behaviors by higher-level libraries and applications.

key-points

Key points

Specter wraps webrtc.rs, which heavily utilizes async Rust. For this reason, many functions cannot be automatically awaited by the caller—the NIF functions send messages across channels to separate threads managed by Rust, which send messages back to Elixir that can be caught by receive or handle_info.

usage

Usage

A process initializes Specter via the init/1 function, which registers the current process for callbacks that may be triggered via webrtc entities.

iex> ## Initialize the library. Register messages to the current pid.
iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
...>
iex> ## Create a peer connection's dependencies
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
...>
iex> Specter.media_engine_exists?(specter, media_engine)
true
iex> Specter.registry_exists?(specter, registry)
true
...>
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
...>
iex> Specter.media_engine_exists?(specter, media_engine)
false
iex> Specter.registry_exists?(specter, registry)
false
...>
iex> ## Create a peer connection
iex> {:ok, pc_1} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc_1}
iex> Specter.peer_connection_exists?(specter, pc_1)
true
iex> ## Add a thing to be negotiated
iex> :ok = Specter.create_data_channel(specter, pc_1, "data")
iex> assert_receive {:data_channel_created, ^pc_1}
...>
iex> ## Create an offer
iex> :ok = Specter.create_offer(specter, pc_1)
iex> assert_receive {:offer, ^pc_1, offer}
iex> :ok = Specter.set_local_description(specter, pc_1, offer)
iex> assert_receive {:ok, ^pc_1, :set_local_description}
...>
iex> ## Create a second peer connection, to answer back
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc_2} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc_2}
...>
iex> ## Begin negotiating offer/answer
iex> :ok = Specter.set_remote_description(specter, pc_2, offer)
iex> assert_receive {:ok, ^pc_2, :set_remote_description}
iex> :ok = Specter.create_answer(specter, pc_2)
iex> assert_receive {:answer, ^pc_2, answer}
iex> :ok = Specter.set_local_description(specter, pc_2, answer)
iex> assert_receive {:ok, ^pc_2, :set_local_description}
...>
iex> ## Receive ice candidates
iex> assert_receive {:ice_candidate, ^pc_1, _candidate}
iex> assert_receive {:ice_candidate, ^pc_2, _candidate}
...>
iex> ## Shut everything down
iex> Specter.close_peer_connection(specter, pc_1)
:ok
iex> assert_receive {:peer_connection_closed, ^pc_1}
...>
iex> :ok = Specter.close_peer_connection(specter, pc_2)
iex> assert_receive {:peer_connection_closed, ^pc_2}

thoughts

Thoughts

During development of the library, it can be assumed that callers will implement handle_info/2 function heads appropriate to the underlying implementation. Once these are more solid, it would be nice to use Specter, which will inject a handle_info/2 callback, and send the messages to other callback functions defined by a behaviour. handle_ice_candidate, and so on.

Some things are returned from the NIF as UUIDs. These are declared as @opaque, to indicate that users of the library should not rely of them being in a particular format. They could change later to be references, for instance.

Link to this section Summary

Types

Options for creating a webrtc answer. Values default to false.

Specter.api_t/0 represent an instantiated API managed in the NIF.

An ICE candidate as JSON.

A uri in the form protocol:host:port, where protocol is either stun or turn.

Options for initializing RTCPeerConnections. This is set during initialization of the library, and later used when creating new connections.

Specter.media_engine_t/0 represents an instantiated MediaEngine managed in the NIF.

native_t/0 references are returned from the NIF, and represent state held in Rust code.

Options for creating a webrtc offer. Values default to false.

Specter.peer_connection_t/0 represents an instantiated RTCPeerConnection managed in the NIF.

Specter.registry_t/0 represent an instantiated intercepter Registry managed in the NIF.

A UTF-8 encoded string encapsulating either an offer or an answer.

The type of an SDP message, either an :offer or an :answer.

A UTF-8 encoded string encapsulating an Offer or an Answer in JSON. The keys are as follows

t()

Specter.t/0 wraps the reference returned from init/1. All functions interacting with NIF state take a Specter.t/0 as their first argument.

Functions

Given an ICE candidate, add it to the given peer connection. Assumes trickle ICE. Candidates must be JSON, with the keys candidate, sdp_mid, sdp_mline_index, and username_fragment.

Closes an open instance of an RTCPeerConnection.

Returns the current configuration for the initialized NIF.

Given an RTCPeerConnection where the remote description has been assigned via set_remote_description/4, create an answer that can be passed to another connection.

Creates a data channel on an RTCPeerConnection.

Given an RTCPeerConnection, create an offer that can be passed to another connection.

Sends back the value of the current session description on a peer connection. This will send back JSON representing an offer or an answer when the peer connection has had set_local_description/3 called and has successfully negotiated ICE. In all other cases, nil will be sent.

Initialize the library. This registers the calling process to receive callback messages to handle_info/2.

Sends back the value of the local session description on a peer connection. This will send back JSON representing an offer or an answer when the peer connection has had set_local_description/3 called. If ICE has been successfully negotated, the current local description will be sent back, otherwise the caller will receive the pending local description.

Returns true or false, depending on whether the media engine is available for consumption, i.e. is initialized and has not been used by a function that takes ownership of it.

An APIBuilder is used to create RTCPeerConnections. This accepts as parameters the output of init/1, new_media_enine/1, and new_registry/2.

Creates a MediaEngine to be configured and used by later function calls. Codecs and other high level configuration are done on instances of MediaEngines. A MediaEngine is combined with a Registry in an entity called an APIBuilder, which is then used to create RTCPeerConnections.

Creates a new RTCPeerConnection, using an API reference created with new_api/3. The functionality wrapped by this function is async, so :ok is returned immediately. Callers should listen for the {:peer_connection_ready, peer_connection_t()} message to receive the results of this function.

Creates an intercepter registry. This is a user configurable RTP/RTCP pipeline, and provides features such as NACKs and RTCP Reports. A registry must be created for each peer connection.

Returns true or false, depending on whether the RTCPeerConnection is initialized.

Sends back the value of the session description on a peer connection that is pending connection, or nil.

Returns true or false, depending on whether the registry is available for consumption, i.e. is initialized and has not been used by a function that takes ownership of it.

Given an offer or an answer session description, sets the local description on a peer connection. The description should be in the form of JSON with the keys type and sdp.

Given an offer or an answer in the form of SDP generated by a remote party, sets the remote description on a peer connection. Expects a session description in the form of JSON with the keys type and sdp.

Link to this section Types

Specs

answer_options_t() :: [] | [{:voice_activity_detection, bool()}]

Options for creating a webrtc answer. Values default to false.

Specs

api_t()

Specter.api_t/0 represent an instantiated API managed in the NIF.

Specs

ice_candidate_t() :: String.t()

An ICE candidate as JSON.

Specs

ice_server() :: String.t()

A uri in the form protocol:host:port, where protocol is either stun or turn.

Defaults to stun:stun.l.google.com:19302.

Specs

init_options() :: [] | [{:ice_servers, [ice_server()]}]

Options for initializing RTCPeerConnections. This is set during initialization of the library, and later used when creating new connections.

Link to this opaque

media_engine_t()

View Source (opaque)

Specs

media_engine_t()

Specter.media_engine_t/0 represents an instantiated MediaEngine managed in the NIF.

Specs

native_t()

native_t/0 references are returned from the NIF, and represent state held in Rust code.

Specs

offer_options_t() ::
  [] | [voice_activity_detection: bool(), ice_restart: bool()]

Options for creating a webrtc offer. Values default to false.

Link to this opaque

peer_connection_t()

View Source (opaque)

Specs

peer_connection_t()

Specter.peer_connection_t/0 represents an instantiated RTCPeerConnection managed in the NIF.

Specs

registry_t()

Specter.registry_t/0 represent an instantiated intercepter Registry managed in the NIF.

Specs

sdp_t() :: String.t()

A UTF-8 encoded string encapsulating either an offer or an answer.

Specs

sdp_type_t() :: :offer | :answer

The type of an SDP message, either an :offer or an :answer.

Link to this type

session_description_t()

View Source

Specs

session_description_t() :: String.t()

A UTF-8 encoded string encapsulating an Offer or an Answer in JSON. The keys are as follows:

keytype
typeoffer, answer
sdp`sdp_t()

Specs

t() :: %Specter{native: native_t()}

Specter.t/0 wraps the reference returned from init/1. All functions interacting with NIF state take a Specter.t/0 as their first argument.

Link to this section Functions

Link to this function

add_ice_candidate(specter, pc, candidate)

View Source

Specs

add_ice_candidate(t(), peer_connection_t(), ice_candidate_t()) ::
  :ok | {:error, term()}

Given an ICE candidate, add it to the given peer connection. Assumes trickle ICE. Candidates must be JSON, with the keys candidate, sdp_mid, sdp_mline_index, and username_fragment.

Link to this function

close_peer_connection(specter, pc)

View Source

Specs

close_peer_connection(t(), peer_connection_t()) :: :ok | {:error, term()}

Closes an open instance of an RTCPeerConnection.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc}
...>
iex> Specter.close_peer_connection(specter, pc)
:ok
iex> {:ok, _pc} =
...>     receive do
...>       {:peer_connection_closed, ^pc} -> {:ok, pc}
...>     after
...>       500 -> {:error, :timeout}
...>     end
...>
iex> Specter.peer_connection_exists?(specter, pc)
false

Specs

config(t()) :: {:ok, Specter.Config.t()} | {:error, term()}

Returns the current configuration for the initialized NIF.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.example.com:3478"])
iex> Specter.config(specter)
{:ok, %Specter.Config{ice_servers: ["stun:stun.example.com:3478"]}}
Link to this function

create_answer(specter, pc, opts \\ [])

View Source

Specs

create_answer(t(), peer_connection_t(), answer_options_t()) ::
  :ok | {:error, term()}

Given an RTCPeerConnection where the remote description has been assigned via set_remote_description/4, create an answer that can be passed to another connection.

paramtypedefault
spectert()
peer_connectionopaque
optionsanswer_options_t()voice_activity_detection: false
Link to this function

create_data_channel(specter, pc, label)

View Source

Specs

create_data_channel(t(), peer_connection_t(), String.t()) ::
  :ok | {:error, term()}

Creates a data channel on an RTCPeerConnection.

Note: this can be useful when attempting to generate a valid offer, but where no media tracks are expected to be sent or received. Callbacks from data channels have not yet been implemented.

Link to this function

create_offer(specter, pc, opts \\ [])

View Source

Specs

create_offer(t(), peer_connection_t(), offer_options_t()) ::
  :ok | {:error, term()}

Given an RTCPeerConnection, create an offer that can be passed to another connection.

paramtypedefault
spectert()
peer_connectionopaque
optionsoffer_options_t()voice_activity_detection: false
ice_restart: false
Link to this function

current_local_description(specter, pc)

View Source

Specs

current_local_description(t(), peer_connection_t()) :: :ok | {:error, term()}

Sends back the value of the current session description on a peer connection. This will send back JSON representing an offer or an answer when the peer connection has had set_local_description/3 called and has successfully negotiated ICE. In all other cases, nil will be sent.

See pending_local_description/2 and local_description/2.

usage

Usage

iex> {:ok, specter} = Specter.init()
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc}
iex> Specter.current_local_description(specter, pc)
:ok
iex> assert_receive {:current_local_description, ^pc, nil}

Specs

init(init_options()) :: {:ok, t()} | {:error, term()}

Initialize the library. This registers the calling process to receive callback messages to handle_info/2.

paramtypedefault
ice_serverslist(String.t())["stun:stun.l.google.com:19302"]

usage

Usage

iex> {:ok, _specter} = Specter.init(ice_servers: ["stun:stun.example.com:3478"])
Link to this function

local_description(specter, pc)

View Source

Specs

local_description(t(), peer_connection_t()) :: :ok | {:error, term()}

Sends back the value of the local session description on a peer connection. This will send back JSON representing an offer or an answer when the peer connection has had set_local_description/3 called. If ICE has been successfully negotated, the current local description will be sent back, otherwise the caller will receive the pending local description.

See current_local_description/2 and pending_local_description/2.

usage

Usage

iex> {:ok, specter} = Specter.init()
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc}
...>
iex> Specter.local_description(specter, pc)
:ok
iex> assert_receive {:local_description, ^pc, nil}
...>
iex> :ok = Specter.create_offer(specter, pc)
iex> assert_receive {:offer, ^pc, offer}
iex> :ok = Specter.set_local_description(specter, pc, offer)
iex> assert_receive {:ok, ^pc, :set_local_description}
...>
iex> Specter.local_description(specter, pc)
:ok
iex> assert_receive {:local_description, ^pc, ^offer}
Link to this function

media_engine_exists?(specter, media_engine)

View Source

Specs

media_engine_exists?(t(), media_engine_t()) :: boolean() | no_return()

Returns true or false, depending on whether the media engine is available for consumption, i.e. is initialized and has not been used by a function that takes ownership of it.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> Specter.media_engine_exists?(specter, media_engine)
true

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> Specter.media_engine_exists?(specter, UUID.uuid4())
false
Link to this function

new_api(specter, media_engine, registry)

View Source

Specs

new_api(t(), media_engine_t(), registry_t()) ::
  {:ok, api_t()} | {:error, term()}

An APIBuilder is used to create RTCPeerConnections. This accepts as parameters the output of init/1, new_media_enine/1, and new_registry/2.

Note that this takes ownership of both the media engine and the registry, effectively consuming them.

paramtypedefault
spectert()
media_engineopaque
registryopaque

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, _api} = Specter.new_api(specter, media_engine, registry)
Link to this function

new_media_engine(specter)

View Source

Specs

new_media_engine(t()) :: {:ok, media_engine_t()} | {:error, term()}

Creates a MediaEngine to be configured and used by later function calls. Codecs and other high level configuration are done on instances of MediaEngines. A MediaEngine is combined with a Registry in an entity called an APIBuilder, which is then used to create RTCPeerConnections.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, _media_engine} = Specter.new_media_engine(specter)
Link to this function

new_peer_connection(specter, api)

View Source

Specs

new_peer_connection(t(), api_t()) ::
  {:ok, peer_connection_t()} | {:error, term()}

Creates a new RTCPeerConnection, using an API reference created with new_api/3. The functionality wrapped by this function is async, so :ok is returned immediately. Callers should listen for the {:peer_connection_ready, peer_connection_t()} message to receive the results of this function.

paramtypedefault
spectert()
apiopaque

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
...>
iex> {:ok, _pc} =
...>     receive do
...>       {:peer_connection_ready, ^pc} -> {:ok, pc}
...>     after
...>       500 -> {:error, :timeout}
...>     end
Link to this function

new_registry(specter, media_engine)

View Source

Specs

new_registry(t(), media_engine_t()) :: {:ok, registry_t()} | {:error, term()}

Creates an intercepter registry. This is a user configurable RTP/RTCP pipeline, and provides features such as NACKs and RTCP Reports. A registry must be created for each peer connection.

The registry may be combined with a MediaEngine in an API (consuming both). The API instance is then used to create RTCPeerConnections.

Note that creating a registry does not take ownership of the media engine.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, _registry} = Specter.new_registry(specter, media_engine)
...>
iex> Specter.media_engine_exists?(specter, media_engine)
true
Link to this function

peer_connection_exists?(specter, peer_connection)

View Source

Specs

peer_connection_exists?(t(), peer_connection_t()) :: boolean() | no_return()

Returns true or false, depending on whether the RTCPeerConnection is initialized.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc}
iex> Specter.peer_connection_exists?(specter, pc)
true

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> Specter.peer_connection_exists?(specter, UUID.uuid4())
false
Link to this function

pending_local_description(specter, pc)

View Source

Specs

pending_local_description(t(), peer_connection_t()) :: :ok | {:error, term()}

Sends back the value of the session description on a peer connection that is pending connection, or nil.

usage

Usage

iex> {:ok, specter} = Specter.init()
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> {:ok, api} = Specter.new_api(specter, media_engine, registry)
iex> {:ok, pc} = Specter.new_peer_connection(specter, api)
iex> assert_receive {:peer_connection_ready, ^pc}
...>
iex> Specter.pending_local_description(specter, pc)
:ok
iex> assert_receive {:pending_local_description, ^pc, nil}
...>
iex> :ok = Specter.create_offer(specter, pc)
iex> assert_receive {:offer, ^pc, offer}
iex> :ok = Specter.set_local_description(specter, pc, offer)
iex> assert_receive {:ok, ^pc, :set_local_description}
...>
iex> Specter.pending_local_description(specter, pc)
:ok
iex> assert_receive {:pending_local_description, ^pc, ^offer}
Link to this function

registry_exists?(specter, registry)

View Source

Specs

registry_exists?(t(), registry_t()) :: boolean() | no_return()

Returns true or false, depending on whether the registry is available for consumption, i.e. is initialized and has not been used by a function that takes ownership of it.

usage

Usage

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> {:ok, media_engine} = Specter.new_media_engine(specter)
iex> {:ok, registry} = Specter.new_registry(specter, media_engine)
iex> Specter.registry_exists?(specter, registry)
true

iex> {:ok, specter} = Specter.init(ice_servers: ["stun:stun.l.google.com:19302"])
iex> Specter.registry_exists?(specter, UUID.uuid4())
false
Link to this function

set_local_description(specter, pc, description)

View Source

Specs

set_local_description(t(), peer_connection_t(), session_description_t()) ::
  :ok | {:error, term()}

Given an offer or an answer session description, sets the local description on a peer connection. The description should be in the form of JSON with the keys type and sdp.

paramtypedefault
spectert/0
peer_connectionopaque
descriptiont:session_description_t()
Link to this function

set_remote_description(specter, pc, description)

View Source

Specs

set_remote_description(t(), peer_connection_t(), session_description_t()) ::
  :ok | {:error, term()}

Given an offer or an answer in the form of SDP generated by a remote party, sets the remote description on a peer connection. Expects a session description in the form of JSON with the keys type and sdp.

paramtypedefault
spectert/0
peer_connectionopaque
descriptionsession_description_t/0