Skip to content

Commit 07b9d6d

Browse files
committed
wip add support for dtmf
1 parent af0ab87 commit 07b9d6d

File tree

13 files changed

+357
-0
lines changed

13 files changed

+357
-0
lines changed

examples/dtmf/.formatter.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

examples/dtmf/.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# If the VM crashes, it generates a dump, let's ignore it too.
14+
erl_crash.dump
15+
16+
# Also ignore archive artifacts (built via "mix archive.build").
17+
*.ez
18+
19+
# Ignore package tarball (built via "mix hex.build").
20+
dtmf-*.tar
21+
22+
# Temporary files, for example, from tests.
23+
/tmp/

examples/dtmf/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Dtmf
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8+
by adding `dtmf` to your list of dependencies in `mix.exs`:
9+
10+
```elixir
11+
def deps do
12+
[
13+
{:dtmf, "~> 0.1.0"}
14+
]
15+
end
16+
```
17+
18+
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19+
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20+
be found at <https://hexdocs.pm/dtmf>.
21+

examples/dtmf/config/config.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Config
2+
3+
config :logger, level: :info
4+
5+
# normally you take these from env variables in `config/runtime.exs`
6+
config :dtmf,
7+
ip: {127, 0, 0, 1},
8+
port: 8829

examples/dtmf/lib/dtmf.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule Dtmf do
2+
use Application
3+
4+
@ip Application.compile_env!(:dtmf, :ip)
5+
@port Application.compile_env!(:dtmf, :port)
6+
7+
@impl true
8+
def start(_type, _args) do
9+
children = [
10+
{Bandit, plug: __MODULE__.Router, ip: @ip, port: @port}
11+
]
12+
13+
Supervisor.start_link(children, strategy: :one_for_one)
14+
end
15+
end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
defmodule Dtmf.PeerHandler do
2+
require Logger
3+
4+
alias ExWebRTC.{
5+
ICECandidate,
6+
MediaStreamTrack,
7+
PeerConnection,
8+
RTPCodecParameters,
9+
SessionDescription
10+
}
11+
12+
@behaviour WebSock
13+
14+
@ice_servers [
15+
%{urls: "stun:stun.l.google.com:19302"}
16+
]
17+
18+
@audio_codecs [
19+
%RTPCodecParameters{
20+
payload_type: 111,
21+
mime_type: "audio/opus",
22+
clock_rate: 48_000,
23+
channels: 2
24+
}
25+
]
26+
27+
@impl true
28+
def init(_) do
29+
{:ok, pc} =
30+
PeerConnection.start_link(
31+
ice_servers: @ice_servers,
32+
video_codecs: [],
33+
audio_codecs: @audio_codecs
34+
)
35+
36+
state = %{
37+
peer_connection: pc,
38+
in_audio_track_id: nil
39+
}
40+
41+
{:ok, state}
42+
end
43+
44+
@impl true
45+
def handle_in({msg, [opcode: :text]}, state) do
46+
msg
47+
|> Jason.decode!()
48+
|> handle_ws_msg(state)
49+
end
50+
51+
@impl true
52+
def handle_info({:ex_webrtc, _from, msg}, state) do
53+
handle_webrtc_msg(msg, state)
54+
end
55+
56+
@impl true
57+
def handle_info({:EXIT, pc, reason}, %{peer_connection: pc} = state) do
58+
# Bandit traps exits under the hood so our PeerConnection.start_link
59+
# won't automatically bring this process down.
60+
Logger.info("Peer connection process exited, reason: #{inspect(reason)}")
61+
{:stop, {:shutdown, :pc_closed}, state}
62+
end
63+
64+
@impl true
65+
def terminate(reason, _state) do
66+
Logger.info("WebSocket connection was terminated, reason: #{inspect(reason)}")
67+
end
68+
69+
defp handle_ws_msg(%{"type" => "offer", "data" => data}, state) do
70+
Logger.info("Received SDP offer:\n#{data["sdp"]}")
71+
72+
offer = SessionDescription.from_json(data)
73+
:ok = PeerConnection.set_remote_description(state.peer_connection, offer)
74+
75+
{:ok, answer} = PeerConnection.create_answer(state.peer_connection)
76+
:ok = PeerConnection.set_local_description(state.peer_connection, answer)
77+
78+
answer_json = SessionDescription.to_json(answer)
79+
80+
msg =
81+
%{"type" => "answer", "data" => answer_json}
82+
|> Jason.encode!()
83+
84+
Logger.info("Sent SDP answer:\n#{answer_json["sdp"]}")
85+
86+
{:push, {:text, msg}, state}
87+
end
88+
89+
defp handle_ws_msg(%{"type" => "ice", "data" => data}, state) do
90+
Logger.info("Received ICE candidate: #{data["candidate"]}")
91+
92+
candidate = ICECandidate.from_json(data)
93+
:ok = PeerConnection.add_ice_candidate(state.peer_connection, candidate)
94+
{:ok, state}
95+
end
96+
97+
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
98+
Logger.info("Connection state changed: #{conn_state}")
99+
100+
if conn_state == :failed do
101+
{:stop, {:shutdown, :pc_failed}, state}
102+
else
103+
{:ok, state}
104+
end
105+
end
106+
107+
defp handle_webrtc_msg({:ice_candidate, candidate}, state) do
108+
candidate_json = ICECandidate.to_json(candidate)
109+
110+
msg =
111+
%{"type" => "ice", "data" => candidate_json}
112+
|> Jason.encode!()
113+
114+
Logger.info("Sent ICE candidate: #{candidate_json["candidate"]}")
115+
116+
{:push, {:text, msg}, state}
117+
end
118+
119+
defp handle_webrtc_msg({:track, %MediaStreamTrack{kind: :audio, id: id}}, state) do
120+
state = %{state | in_audio_track_id: id}
121+
{:ok, state}
122+
end
123+
124+
defp handle_webrtc_msg({:rtp, id, nil, packet}, %{in_audio_track_id: id} = state) do
125+
dbg(packet)
126+
{:ok, state}
127+
end
128+
129+
defp handle_webrtc_msg(_msg, state), do: {:ok, state}
130+
end

examples/dtmf/lib/dtmf/router.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule Dtmf.Router do
2+
use Plug.Router
3+
4+
plug(Plug.Static, at: "/", from: :dtmf)
5+
plug(:match)
6+
plug(:dispatch)
7+
8+
get "/ws" do
9+
WebSockAdapter.upgrade(conn, Dtmf.PeerHandler, %{}, [])
10+
end
11+
12+
match _ do
13+
send_resp(conn, 404, "not found")
14+
end
15+
end

examples/dtmf/mix.exs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
defmodule Dtmf.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :dtmf,
7+
version: "0.1.0",
8+
elixir: "~> 1.18",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger],
18+
mod: {Dtmf, []}
19+
]
20+
end
21+
22+
# Run "mix help deps" to learn about dependencies.
23+
defp deps do
24+
[
25+
{:plug, "~> 1.15.0"},
26+
{:bandit, "~> 1.2.0"},
27+
{:websock_adapter, "~> 0.5.0"},
28+
{:jason, "~> 1.4.0"},
29+
{:ex_webrtc, path: "../../."}
30+
]
31+
end
32+
end

examples/dtmf/mix.lock

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
%{
2+
"bandit": {:hex, :bandit, "1.2.3", "a98d664a96fec23b68e776062296d76a94b4459795b38209f4ae89cb4225709c", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e29150245a9b5f56944434e5240966e75c917dad248f689ab589b32187a81af"},
3+
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},
4+
"bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"},
5+
"bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"},
6+
"crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"},
7+
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
8+
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
9+
"ex_dtls": {:hex, :ex_dtls, "0.17.0", "dbe1d494583a307c26148cb5ea5d7c14e65daa8ec96cc73002cc3313ce4b9a81", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "3eaa7221ec08fa9e4bc9430e426cbd5eb4feb8d8f450b203cf39b2114a94d713"},
10+
"ex_ice": {:hex, :ex_ice, "0.12.0", "b52ec3ff878d5fb632ef9facc7657dfdf59e2ff9f23e634b0918e6ce1a05af48", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.2.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "a86024a5fbf9431082784be4bb3606d3cde9218fb325a9f208ccd6e0abfd0d73"},
11+
"ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"},
12+
"ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"},
13+
"ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},
14+
"ex_sdp": {:hex, :ex_sdp, "1.1.1", "1a7b049491e5ec02dad9251c53d960835dc5631321ae978ec331831f3e4f6d5f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "1b13a72ac9c5c695b8824dbdffc671be8cbb4c0d1ccb4ff76a04a6826759f233"},
15+
"ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"},
16+
"ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"},
17+
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
18+
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
19+
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
20+
"membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"},
21+
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
22+
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
23+
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
24+
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
25+
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
26+
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
27+
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
28+
"req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
29+
"shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"},
30+
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
31+
"thousand_island": {:hex, :thousand_island, "1.3.13", "d598c609172275f7b1648c9f6eddf900e42312b09bfc2f2020358f926ee00d39", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5a34bdf24ae2f965ddf7ba1a416f3111cfe7df50de8d66f6310e01fc2e80b02a"},
32+
"unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"},
33+
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
34+
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
35+
"zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"},
36+
}

examples/dtmf/priv/static/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
8+
<title>Elixir WebRTC Dtmf Example</title>
9+
</head>
10+
11+
<body>
12+
<h1>Elixir WebRTC Dtmf Example</h1>
13+
<audio id="audioPlayer" autoplay controls></audio>
14+
<script src="script.js"></script>
15+
</body>
16+
17+
</html>

examples/dtmf/priv/static/script.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const pcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
2+
const mediaConstraints = { video: false, audio: true };
3+
const audioPlayer = document.getElementById("audioPlayer");
4+
5+
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
6+
const ws = new WebSocket(`${proto}//${window.location.host}/ws`);
7+
ws.onopen = (_) => start_connection(ws);
8+
ws.onclose = (event) =>
9+
console.log("WebSocket connection was terminated:", event);
10+
11+
const start_connection = async (ws) => {
12+
const pc = new RTCPeerConnection(pcConfig);
13+
// expose pc for easier debugging and experiments
14+
window.pc = pc;
15+
pc.ontrack = (event) => (audioPlayer.srcObject = event.streams[0]);
16+
pc.onicecandidate = (event) => {
17+
if (event.candidate === null) return;
18+
19+
console.log("Sent ICE candidate:", event.candidate);
20+
ws.send(JSON.stringify({ type: "ice", data: event.candidate }));
21+
};
22+
23+
const localStream = await navigator.mediaDevices.getUserMedia(
24+
mediaConstraints
25+
);
26+
pc.addTrack(localStream.getAudioTracks()[0]);
27+
audioPlayer.srcObject = localStream;
28+
29+
ws.onmessage = async (event) => {
30+
const { type, data } = JSON.parse(event.data);
31+
32+
switch (type) {
33+
case "answer":
34+
console.log("Received SDP answer:", data);
35+
await pc.setRemoteDescription(data);
36+
break;
37+
case "ice":
38+
console.log("Received ICE candidate:", data);
39+
await pc.addIceCandidate(data);
40+
}
41+
};
42+
43+
const offer = await pc.createOffer();
44+
await pc.setLocalDescription(offer);
45+
console.log("Sent SDP offer:", offer);
46+
ws.send(JSON.stringify({ type: "offer", data: offer }));
47+
};

examples/dtmf/test/dtmf_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule DtmfTest do
2+
use ExUnit.Case
3+
doctest Dtmf
4+
5+
test "greets the world" do
6+
assert Dtmf.hello() == :world
7+
end
8+
end

examples/dtmf/test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)