diff --git a/lib/ex_webrtc/app.ex b/lib/ex_webrtc/app.ex new file mode 100644 index 00000000..4b6cb8ca --- /dev/null +++ b/lib/ex_webrtc/app.ex @@ -0,0 +1,9 @@ +defmodule ExWebRTC.App do + use Application + + def start(_type, _args) do + IO.inspect(:loading_ex_webrtc_app) + children = [{Registry, keys: :unique, name: ExWebRTC.Registry}] + Supervisor.start_link(children, strategy: :one_for_one) + end +end diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 2a9909de..c03aa10d 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -49,6 +49,11 @@ defmodule ExWebRTC.DTLSTransport do GenServer.call(dtls_transport, :set_ice_connected) end + @spec get_local_cert_info(dtls_transport()) :: map() + def get_local_cert_info(dtls_transport) do + GenServer.call(dtls_transport, :get_local_cert_info) + end + @spec get_fingerprint(dtls_transport()) :: binary() def get_fingerprint(dtls_transport) do GenServer.call(dtls_transport, :get_fingerprint) @@ -87,6 +92,7 @@ defmodule ExWebRTC.DTLSTransport do ice_connected: false, buffered_packets: nil, cert: cert, + base64_cert: Base.encode64(cert), pkey: pkey, fingerprint: fingerprint, in_srtp: ExLibSRTP.new(), @@ -133,6 +139,17 @@ defmodule ExWebRTC.DTLSTransport do end end + @impl true + def handle_call(:get_local_cert_info, _from, state) do + cert_info = %{ + fingerprint: state.fingerprint, + fingerprint_algorithm: :sha_256, + base64_certificate: state.base64_cert + } + + {:reply, cert_info, state} + end + @impl true def handle_call(:get_fingerprint, _from, state) do {:reply, state.fingerprint, state} diff --git a/lib/ex_webrtc/ice_transport.ex b/lib/ex_webrtc/ice_transport.ex index 32efebb0..49110093 100644 --- a/lib/ex_webrtc/ice_transport.ex +++ b/lib/ex_webrtc/ice_transport.ex @@ -14,6 +14,7 @@ defmodule ExWebRTC.ICETransport do @callback restart(pid()) :: :ok @callback send_data(pid(), binary()) :: :ok @callback set_remote_credentials(pid(), ufrag :: binary(), pwd :: binary()) :: :ok + @callback get_stats(pid()) :: map() @callback stop(pid()) :: :ok end @@ -43,5 +44,7 @@ defmodule ExWebRTC.DefaultICETransport do @impl true defdelegate set_remote_credentials(pid, ufrag, pwd), to: ICEAgent @impl true + defdelegate get_stats(pid), to: ICEAgent + @impl true defdelegate stop(pid), to: ICEAgent end diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index c381fea6..da588c92 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -18,6 +18,7 @@ defmodule ExWebRTC.PeerConnection do MediaStreamTrack, RTPTransceiver, RTPSender, + RTPReceiver, SDPUtils, SessionDescription, Utils @@ -59,6 +60,11 @@ defmodule ExWebRTC.PeerConnection do @type connection_state() :: :closed | :failed | :disconnected | :new | :connecting | :connected #### API #### + @spec get_all_peer_connections() :: [pid()] + def get_all_peer_connections() do + Registry.select(ExWebRTC.Registry, [{{:_, :"$1", :_}, [], [:"$1"]}]) + end + @spec start_link(Configuration.options()) :: GenServer.on_start() def start_link(options \\ []) do configuration = Configuration.from_options!(options) @@ -157,6 +163,11 @@ defmodule ExWebRTC.PeerConnection do GenServer.call(peer_connection, {:remove_track, sender_id}) end + @spec get_stats(peer_connection()) :: %{String.t() => term()} + def get_stats(peer_connection) do + GenServer.call(peer_connection, :get_stats) + end + @spec send_rtp(peer_connection(), String.t(), ExRTP.Packet.t()) :: :ok def send_rtp(peer_connection, track_id, packet) do GenServer.cast(peer_connection, {:send_rtp, track_id, packet}) @@ -171,6 +182,7 @@ defmodule ExWebRTC.PeerConnection do @impl true def init({owner, config}) do + {:ok, _} = Registry.register(ExWebRTC.Registry, self(), self()) ice_config = [stun_servers: config.ice_servers, ip_filter: config.ice_ip_filter, on_data: nil] {:ok, ice_pid} = DefaultICETransport.start_link(:controlled, ice_config) {:ok, dtls_transport} = DTLSTransport.start_link(DefaultICETransport, ice_pid) @@ -182,6 +194,7 @@ defmodule ExWebRTC.PeerConnection do state = %{ owner: owner, + stats_id: Utils.generate_id(), config: config, current_local_desc: nil, pending_local_desc: nil, @@ -558,6 +571,40 @@ defmodule ExWebRTC.PeerConnection do end end + @impl true + def handle_call(:get_stats, _from, state) do + timestamp = System.os_time(:millisecond) + + cert_info = DTLSTransport.get_local_cert_info(state.dtls_transport) + + stats = + Enum.flat_map(state.transceivers, fn tr -> + inbound_rtp_stats = RTPSender.get_stats(tr.sender, timestamp) + outbound_rtp_stats = RTPReceiver.get_stats(tr.receiver, timestamp) + [inbound_rtp_stats, outbound_rtp_stats] + end) + + stats = %{ + state.stats_id => %{ + id: state.stats_id, + type: :peer_connection, + timestamp: timestamp, + datachannels_opened: 0, + datachannels_closed: 0 + }, + :certificate => %{ + id: :certificate, + type: :certificate, + timestamp: timestamp, + fingerprint: Utils.hex_dump(cert_info.fingerprint), + fingerprint_algorithm: cert_info.fingerprint_algorithm, + base64_certificate: cert_info.base64_certificate + } + } + + {:reply, stats, state} + end + @impl true def handle_cast({:send_rtp, track_id, packet}, state) do # TODO: iterating over transceivers is not optimal diff --git a/lib/ex_webrtc/rtp_receiver.ex b/lib/ex_webrtc/rtp_receiver.ex index 82304531..0b8a50e5 100644 --- a/lib/ex_webrtc/rtp_receiver.ex +++ b/lib/ex_webrtc/rtp_receiver.ex @@ -10,4 +10,6 @@ defmodule ExWebRTC.RTPReceiver do } defstruct [:track] + + def get_stats(_, _), do: %{} end diff --git a/lib/ex_webrtc/rtp_sender.ex b/lib/ex_webrtc/rtp_sender.ex index e0a03728..45383d8e 100644 --- a/lib/ex_webrtc/rtp_sender.ex +++ b/lib/ex_webrtc/rtp_sender.ex @@ -19,11 +19,23 @@ defmodule ExWebRTC.RTPSender do mid: String.t() | nil, pt: non_neg_integer() | nil, ssrc: non_neg_integer() | nil, - last_seq_num: non_neg_integer() + last_seq_num: non_neg_integer(), + packets_sent: non_neg_integer(), + bytes_sent: non_neg_integer() } @enforce_keys [:id, :last_seq_num] - defstruct @enforce_keys ++ [:track, :codec, :mid, :pt, :ssrc, rtp_hdr_exts: %{}] + defstruct @enforce_keys ++ + [ + :track, + :codec, + :mid, + :pt, + :ssrc, + rtp_hdr_exts: %{}, + packets_sent: 0, + bytes_sent: 0 + ] @doc false @spec new( @@ -47,7 +59,9 @@ defmodule ExWebRTC.RTPSender do pt: pt, ssrc: ssrc, last_seq_num: random_seq_num(), - mid: mid + mid: mid, + packets_sent: 0, + bytes_sent: 0 } end @@ -82,9 +96,29 @@ defmodule ExWebRTC.RTPSender do |> ExRTP.Packet.add_extension(mid_ext) |> ExRTP.Packet.encode() - sender = %{sender | last_seq_num: next_seq_num} + sender = %{ + sender + | last_seq_num: next_seq_num, + packets_sent: sender.packets_sent + 1, + bytes_sent: sender.bytes_sent + byte_size(packet) + } + {packet, sender} end + @doc false + @spec get_stats(t(), non_neg_integer()) :: map() + def get_stats(sender, timestamp) do + %{ + timestamp: timestamp, + type: :outbound_rtp, + id: sender.id, + ssrc: sender.ssrc, + kind: :audio, + packets_sent: sender.packets_sent, + bytes_sent: sender.bytes_sent + } + end + defp random_seq_num(), do: Enum.random(0..65_535) end diff --git a/mix.exs b/mix.exs index e7360c81..7a04a847 100644 --- a/mix.exs +++ b/mix.exs @@ -33,6 +33,7 @@ defmodule ExWebRTC.MixProject do def application do [ + mod: {ExWebRTC.App, []}, extra_applications: [:logger] ] end