Skip to content

Commit

Permalink
Add testing example and basis for remote description processing
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala committed Sep 22, 2023
1 parent 662a180 commit 0401fad
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 75 deletions.
104 changes: 104 additions & 0 deletions examples/example.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
Mix.install([{:gun, "~> 2.0.1"}, {:ex_webrtc, path: "./", force: true}, {:jason, "~> 1.4.0"}])

require Logger
Logger.configure(level: :info)

defmodule Peer do
use GenServer

require Logger

alias ExWebRTC.{PeerConnection, SessionDescription}

@ice_servers [
%{urls: "stun:stun.stunprotocol.org:3478"},
%{urls: "stun:stun.l.google.com:19302"}
]

def start_link() do
GenServer.start_link(__MODULE__, [])
end

@impl true
def init(_) do
{:ok, conn} = :gun.open({127, 0, 0, 1}, 4000)
{:ok, _protocol} = :gun.await_up(conn)
:gun.ws_upgrade(conn, "/websocket")

receive do
{:gun_upgrade, ^conn, stream, _, _} ->
Logger.info("Connected to the signalling server")
Process.send_after(self(), :ws_ping, 1000)

{:ok, pc} = PeerConnection.start_link(
bundle_policy: :max_bundle,
ice_servers: @ice_servers
)

{:ok, %{conn: conn, stream: stream, peer_connection: pc}}

other ->
Logger.error("Couldn't connect to the signalling server: #{inspect(other)}")
exit(:error)
end
end

@impl true
def handle_info({:gun_down, _, :ws, :closed, _}, state) do
Logger.info("Server closed ws connection. Exiting")
{:stop, :normal, state}
end

@impl true
def handle_info(:ws_ping, state) do
Process.send_after(self(), :ws_ping, 1000)
:gun.ws_send(state.conn, state.stream, :ping)
{:noreply, state}
end

@impl true
def handle_info({:gun_ws, _, _, {:text, msg}}, state) do
msg
|> Jason.decode!()
|> handle_ws_message(state.peer_connection)

{:noreply, state}
end

@impl true
def handle_info({:gun_ws, _, _, {:close, code}}, _state) do
Logger.info("Signalling connection closed with code: #{code}. Exiting")
exit(:ws_down)
end

@impl true
def handle_info(msg, state) do
Logger.warning("Received unknown msg: #{inspect(msg)}")
{:noreply, state}
end

defp handle_ws_message(%{type: "offer", data: data}, pc) do
Logger.info("Received SDP offer: #{data}")
{:ok, desc} = SessionDescription.from_init(data)
PeerConnection.addRemoteDescription(desc)
end

defp handle_ws_message(%{type: "ice", data: data}, pc) do
Logger.info("Received remote ICE candidate: #{data}")
end

defp handle_ws_message(msg, _pc) do
Logger.info("Received unexpected message: #{inspect(msg)}")
end
end

{:ok, pid} = Peer.start_link()
ref = Process.monitor(pid)

receive do
{:DOWN, ^ref, _, _, _} ->
Logger.info("Peer process closed. Exiting")

other ->
Logger.warning("Unexpected msg. Exiting. Msg: #{inspect(other)}")
end
15 changes: 15 additions & 0 deletions examples/example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>PeerConnection Example</title>
</head>
<body>
<main>
<h1>PeerConnection Example</h1>
</main>
<script src="example.js"></script>
</body>
</html>
51 changes: 51 additions & 0 deletions examples/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const pcConfig = {
'iceServers': [
{'urls': 'stun:stun.stunprotocol.org:3478'},
{'urls': 'stun:stun.l.google.com:19302'},
]
};

const mediaConstraints = {
audio: true
};

const start_connection = async (ws) => {
const pc = new RTCPeerConnection(pcConfig);

pc.onicecandidate = event => {
console.log("New local ICE candidate:", event.candidate);

if (event.candidate !== null) {
ws.send(JSON.stringify({type: "ice", data: event.candidate.candidate}));
}
};

pc.ontrack = null; // TODO

ws.onmessage = event => {
const msg = JSON.parse(event.data);
console.log("Received message:", msg);

if (msg.type === "answer") {
console.log("Received SDP answer:", msg.data);
pc.setRemoteDescription(msg.data);
} else if (msg.type === "ice") {
console.log("Received ICE candidate:", msg.data);
pc.addIceCandidate(msg.data);
}
};

const localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
for (const track of localStream.getTracks()) {
pc.addTrack(track, localStream);
}

const desc = await pc.createOffer();
console.log("Generated SDP offer:", desc);
await pc.setLocalDescription(desc);

ws.send(JSON.stringify({type: "offer", data: desc.sdp}))
};

const ws = new WebSocket("ws://127.0.0.1:4000/websocket");
ws.onopen = _ => start_connection(ws);
9 changes: 0 additions & 9 deletions examples/simple.exs

This file was deleted.

55 changes: 34 additions & 21 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,40 +70,53 @@ defmodule ExWebRTC.PeerConnection do
end

@impl true
def handle_call({:create_offer, options}, _from, state) do
_ice_restart = Keyword.get(options, :ice_restart, false)

# TODO probably will need to move SDP stuff to its module
sdp =
%{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}
|> ExSDP.add_attribute({:ice_options, "trickle"})

sdp = Enum.reduce(state.transceivers, sdp, &add_media_description/2)

desc = %SessionDescription{type: :offer, sdp: to_string(sdp)}
{:reply, {:ok, desc}, state}
def handle_call({:create_offer, _options}, _from, state) do
{:reply, :ok, state}
end

@impl true
def handle_call({:create_answer, _options}, _from, state) do
# TODO
{:reply, :ok, state}
end

@impl true
def handle_call({:set_local_description, _desc}, _from, state) do
# TODO
def handle_call({:set_local_description, desc}, _from, state) do

Check warning on line 83 in lib/ex_webrtc/peer_connection.ex

View workflow job for this annotation

GitHub Actions / Test (OTP 26 / Elixir 1.15)

variable "desc" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 83 in lib/ex_webrtc/peer_connection.ex

View workflow job for this annotation

GitHub Actions / Lint (OTP 26 / Elixir 1.15)

variable "desc" is unused (if the variable is not meant to be used, prefix it with an underscore)
{:reply, :ok, state}
end

@impl true
def handle_call({:set_remote_description, _desc}, _from, state) do
# TODO
{:reply, :ok, state}
def handle_call({:set_remote_description, desc}, _from, state) do
%SessionDescription{type: type, sdp: sdp} = desc

cond do
# TODO handle rollback
type == :rollback ->
{:reply, :ok, state}

valid_transition?(:remote, state.signaling_state, type) ->
with {:ok, sdp} <- ExSDP.parse(sdp),
{:ok, state} <- apply_remote_description(type, sdp, state) do
{:reply, :ok, state}
end

true ->
{:reply, :error, state}
end
end

defp add_media_description(_transceiver, sdp) do
# TODO
sdp
defp apply_remote_description(_type, _sdp, state) do
{:ok, state}
end

defp valid_transition?(_, _, :rollback), do: false

defp valid_transition?(:remote, state, :offer)
when state in [:stable, :have_remote_offer],
do: true

defp valid_transition?(:remote, state, type)
when state in [:have_local_offer, :have_remote_pranswer] and type in [:answer, :pranswer],
do: true

defp valid_transition?(:remote, _, _), do: false
end
14 changes: 11 additions & 3 deletions lib/ex_webrtc/peer_connection/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ defmodule ExWebRTC.PeerConnection.Configuration do
| :max_compat
| :max_bundle

@type ice_server() :: %{
optional(:credential) => String.t(),
optional(:username) => String.t(),
:urls => [String.t()] | String.t()
}

# TODO implement
@type certificate() :: :TODO

@type ice_transport_policy() ::
:all
| :relay
Expand All @@ -16,10 +25,9 @@ defmodule ExWebRTC.PeerConnection.Configuration do

@type t() :: %__MODULE__{
bundle_policy: bundle_policy(),
# TODO certs type
certificates: term(),
certificates: [certificate()],
ice_candidate_pool_size: non_neg_integer(),
ice_servers: [String.t()],
ice_servers: [ice_server()],
ice_transport_policy: ice_transport_policy(),
peer_identity: String.t(),
rtcp_mux_policy: rtcp_mux_policy()
Expand Down
10 changes: 0 additions & 10 deletions lib/ex_webrtc/peer_connection/signaling_state.ex

This file was deleted.

31 changes: 0 additions & 31 deletions lib/ex_webrtc/peer_connection/transceiver.ex

This file was deleted.

10 changes: 10 additions & 0 deletions lib/ex_webrtc/session_description.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@ defmodule ExWebRTC.SessionDescription do

@enforce_keys [:type, :sdp]
defstruct @enforce_keys

@spec from_init(%{String.t() => String.t()}) :: {:ok, t()} | :error
def from_init(%{"type" => type})
when type not in ["answer", "offer", "pranswer", "rollback"],
do: :error

def from_init(%{"type" => type, "sdp" => sdp}) do
type = String.to_atom(type)
{:ok, %__MODULE__{type: type, sdp: sdp}}
end
end
1 change: 0 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ defmodule ExWebRTC.MixProject do
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},

{:ex_sdp, "~> 0.11"},
{:ex_ice, "~> 0.1"}
]
Expand Down

0 comments on commit 0401fad

Please sign in to comment.