View Source Specter (specter v0.2.0)
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
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.
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.
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
.
Specs
session_description_t() :: String.t()
A UTF-8 encoded string encapsulating an Offer or an Answer in JSON. The keys are as follows:
key | type |
---|---|
type | offer , 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
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
.
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"]}}
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.
param | type | default |
---|---|---|
specter | t() | |
peer_connection | opaque | |
options | answer_options_t() | voice_activity_detection: false |
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.
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.
param | type | default |
---|---|---|
specter | t() | |
peer_connection | opaque | |
options | offer_options_t() | voice_activity_detection: false |
ice_restart: false |
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
.
param | type | default |
---|---|---|
ice_servers | list(String.t()) | ["stun:stun.l.google.com:19302"] |
usage
Usage
iex> {:ok, _specter} = Specter.init(ice_servers: ["stun:stun.example.com:3478"])
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}
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
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.
param | type | default |
---|---|---|
specter | t() | |
media_engine | opaque | |
registry | opaque |
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)
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)
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.
param | type | default |
---|---|---|
specter | t() | |
api | opaque |
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
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
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
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}
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
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
.
param | type | default |
---|---|---|
specter | t/0 | |
peer_connection | opaque | |
description | t:session_description_t() |
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
.
param | type | default |
---|---|---|
specter | t/0 | |
peer_connection | opaque | |
description | session_description_t/0 |