From fff797c86e4b7fa11ff0dacd55a0d6c4aa9b2799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 3 Mar 2025 17:19:11 +0100 Subject: [PATCH] Don't terminate PeerConnection after moving to the failed state --- examples/chat/lib/chat/peer_handler.ex | 10 ++++++ examples/chat/mix.lock | 12 +++---- examples/chat/priv/static/script.js | 2 ++ examples/echo/lib/echo/peer_handler.ex | 10 ++++++ examples/echo/priv/static/script.js | 2 ++ .../lib/save_to_file/peer_handler.ex | 10 ++++++ examples/save_to_file/priv/static/script.js | 2 ++ .../lib/send_from_file/peer_handler.ex | 10 ++++++ examples/send_from_file/priv/static/script.js | 2 ++ examples/whip_whep/lib/whip_whep/forwarder.ex | 10 ++++++ .../lib/whip_whep/peer_supervisor.ex | 4 +-- examples/whip_whep/priv/static/script.js | 2 ++ lib/ex_webrtc/dtls_transport.ex | 28 +++++++++++---- lib/ex_webrtc/peer_connection.ex | 36 ++++++++++++------- 14 files changed, 114 insertions(+), 26 deletions(-) diff --git a/examples/chat/lib/chat/peer_handler.ex b/examples/chat/lib/chat/peer_handler.ex index f1939317..8537cfb5 100644 --- a/examples/chat/lib/chat/peer_handler.ex +++ b/examples/chat/lib/chat/peer_handler.ex @@ -103,6 +103,16 @@ defmodule Chat.PeerHandler do {:push, {:text, msg}, state} end + defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do + Logger.info("Connection state changed: #{conn_state}") + + if conn_state == :failed do + {:stop, {:shutdown, :pc_failed}, state} + else + {:ok, state} + end + end + defp handle_webrtc_msg({:data_channel, %DataChannel{ref: ref}}, state) do state = %{state | channel_ref: ref} {:ok, state} diff --git a/examples/chat/mix.lock b/examples/chat/mix.lock index 27ae7745..14fcd59e 100644 --- a/examples/chat/mix.lock +++ b/examples/chat/mix.lock @@ -8,12 +8,12 @@ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, - "ex_ice": {:hex, :ex_ice, "0.9.0", "d1a7e31b9cc52faf668f001f870344d3f9094955bafb6af62d84b7b4c2dd6b36", [: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", "8f256faeb9cc5409d2177e68918198c7ef64372a729c8e1590699546554419aa"}, + "ex_ice": {:hex, :ex_ice, "0.9.4", "793121989164e49d8dc64b82bcb7842a4c2e0d224a2f00379ab415293a78c8e7", [: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", "fc328ed721c558440266def81a2cd5138d163164218ebe449fa9a10fcda72574"}, "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"}, "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"}, - "ex_sctp": {:hex, :ex_sctp, "0.1.0", "c27075ff0d39ae66fca6570039537c73494ac1771355d4536d83f1e015681168", [:mix], [{:rustler, "~> 0.34.0", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "722197960d2c2682f32d4d8f3ff46f106e71d34213e4c377092e819b92c317ac"}, - "ex_sdp": {:hex, :ex_sdp, "1.1.0", "a93d72d00704efd83f7e144e4ca9822ca4aea5b5d851353d092de40e1ad0ecdc", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "f5c033abcda958a9b090210f9429f24b74b003c28c24175c58a033a5205a1cfe"}, + "ex_sctp": {:hex, :ex_sctp, "0.1.2", "ee004e114bf0a485058b8caedfa7d01125e845782647a19e3fd99f5d086a9a8f", [:mix], [{:rustler, "~> 0.36.0", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "cc752b5bee8ff730a3d9c8899a921e1ba78009fd2904f94a0e6d89c9b76860ed"}, + "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"}, "ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"}, "ex_turn": {:hex, :ex_turn, "0.2.0", "4e1f9b089e9a5ee44928d12370cc9ea7a89b84b2f6256832de65271212eb80de", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "08e884f0af2c4a147e3f8cd4ffe33e3452a256389f0956e55a8c4d75bf0e74cd"}, "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"}, @@ -21,17 +21,17 @@ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "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"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [: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", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "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"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "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"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [: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", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, - "rustler": {:hex, :rustler, "0.34.0", "e9a73ee419fc296a10e49b415a2eb87a88c9217aa0275ec9f383d37eed290c1c", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "1d0c7449482b459513003230c0e2422b0252245776fe6fd6e41cb2b11bd8e628"}, + "rustler": {:hex, :rustler, "0.36.1", "2d4b1ff57ea2789a44756a40dbb5fbb73c6ee0a13d031dcba96d0a5542598a6a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.7", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "f3fba4ad272970e0d1bc62972fc4a99809651e54a125c5242de9bad4574b2d02"}, "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"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"}, + "thousand_island": {:hex, :thousand_island, "1.3.11", "b68f3e91f74d564ae20b70d981bbf7097dde084343c14ae8a33e5b5fbb3d6f37", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "555c18c62027f45d9c80df389c3d01d86ba11014652c00be26e33b1b64e98d29"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "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"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/examples/chat/priv/static/script.js b/examples/chat/priv/static/script.js index 7129de8a..31259312 100644 --- a/examples/chat/priv/static/script.js +++ b/examples/chat/priv/static/script.js @@ -9,6 +9,8 @@ ws.onclose = event => console.log("WebSocket connection was terminated:", event) const start_connection = async (ws) => { const pc = new RTCPeerConnection(pcConfig); + // expose pc for easier debugging and experiments + window.pc = pc; pc.onicecandidate = event => { if (event.candidate === null) return; diff --git a/examples/echo/lib/echo/peer_handler.ex b/examples/echo/lib/echo/peer_handler.ex index c2f0c47d..550c2155 100644 --- a/examples/echo/lib/echo/peer_handler.ex +++ b/examples/echo/lib/echo/peer_handler.ex @@ -112,6 +112,16 @@ defmodule Echo.PeerHandler do {:ok, state} end + defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do + Logger.info("Connection state changed: #{conn_state}") + + if conn_state == :failed do + {:stop, {:shutdown, :pc_failed}, state} + else + {:ok, state} + end + end + defp handle_webrtc_msg({:ice_candidate, candidate}, state) do candidate_json = ICECandidate.to_json(candidate) diff --git a/examples/echo/priv/static/script.js b/examples/echo/priv/static/script.js index bd34b041..23f5b503 100644 --- a/examples/echo/priv/static/script.js +++ b/examples/echo/priv/static/script.js @@ -10,6 +10,8 @@ ws.onclose = event => console.log("WebSocket connection was terminated:", event) const start_connection = async (ws) => { const pc = new RTCPeerConnection(pcConfig); + // expose pc for easier debugging and experiments + window.pc = pc; pc.ontrack = event => videoPlayer.srcObject = event.streams[0]; pc.onicecandidate = event => { if (event.candidate === null) return; diff --git a/examples/save_to_file/lib/save_to_file/peer_handler.ex b/examples/save_to_file/lib/save_to_file/peer_handler.ex index 267d7450..d69622be 100644 --- a/examples/save_to_file/lib/save_to_file/peer_handler.ex +++ b/examples/save_to_file/lib/save_to_file/peer_handler.ex @@ -133,6 +133,16 @@ defmodule SaveToFile.PeerHandler do {:ok, state} end + defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do + Logger.info("Connection state changed: #{conn_state}") + + if conn_state == :failed do + {:stop, {:shutdown, :pc_failed}, state} + else + {:ok, state} + end + end + defp handle_webrtc_msg({:ice_candidate, candidate}, state) do candidate_json = ICECandidate.to_json(candidate) diff --git a/examples/save_to_file/priv/static/script.js b/examples/save_to_file/priv/static/script.js index b2f26806..48311e67 100644 --- a/examples/save_to_file/priv/static/script.js +++ b/examples/save_to_file/priv/static/script.js @@ -18,6 +18,8 @@ button.onclick = () => { const start_connection = async (ws) => { const pc = new RTCPeerConnection(pcConfig); + // expose pc for easier debugging and experiments + window.pc = pc; pc.onicecandidate = event => { if (event.candidate === null) return; diff --git a/examples/send_from_file/lib/send_from_file/peer_handler.ex b/examples/send_from_file/lib/send_from_file/peer_handler.ex index 2ea817cd..568c0e92 100644 --- a/examples/send_from_file/lib/send_from_file/peer_handler.ex +++ b/examples/send_from_file/lib/send_from_file/peer_handler.ex @@ -246,5 +246,15 @@ defmodule SendFromFile.PeerHandler do {:ok, state} end + defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do + Logger.info("Connection state changed: #{conn_state}") + + if conn_state == :failed do + {:stop, {:shutdown, :pc_failed}, state} + else + {:ok, state} + end + end + defp handle_webrtc_msg(_msg, state), do: {:ok, state} end diff --git a/examples/send_from_file/priv/static/script.js b/examples/send_from_file/priv/static/script.js index bcb8cb89..735ec4eb 100644 --- a/examples/send_from_file/priv/static/script.js +++ b/examples/send_from_file/priv/static/script.js @@ -9,6 +9,8 @@ ws.onclose = event => console.log("WebSocket connection was terminated:", event) const start_connection = async (ws) => { const pc = new RTCPeerConnection(pcConfig); + // expose pc for easier debugging and experiments + window.pc = pc; pc.ontrack = event => videoPlayer.srcObject = event.streams[0]; pc.onicecandidate = event => { if (event.candidate === null) return; diff --git a/examples/whip_whep/lib/whip_whep/forwarder.ex b/examples/whip_whep/lib/whip_whep/forwarder.ex index 00c591b6..cc96bf8c 100644 --- a/examples/whip_whep/lib/whip_whep/forwarder.ex +++ b/examples/whip_whep/lib/whip_whep/forwarder.ex @@ -91,6 +91,16 @@ defmodule WhipWhep.Forwarder do {:noreply, state} end + @impl true + def handle_info({:ex_webrtc, pc, {:connection_state_change, :failed}}, state) do + Logger.info("Output peer connection (#{inspect(pc)}) state change: failed. Removing.") + :ok = PeerConnection.close(pc) + pending_outputs = List.delete(state.pending_outputs, pc) + outputs = Map.delete(state.outputs, pc) + state = %{state | pending_outputs: pending_outputs, outputs: outputs} + {:noreply, state} + end + @impl true def handle_info( {:ex_webrtc, input_pc, {:rtp, id, nil, packet}}, diff --git a/examples/whip_whep/lib/whip_whep/peer_supervisor.ex b/examples/whip_whep/lib/whip_whep/peer_supervisor.ex index 589474d0..91bbb292 100644 --- a/examples/whip_whep/lib/whip_whep/peer_supervisor.ex +++ b/examples/whip_whep/lib/whip_whep/peer_supervisor.ex @@ -69,7 +69,7 @@ defmodule WhipWhep.PeerSupervisor do {:ok, pc, pc_id, answer.sdp} else - {:error, _res} = err -> + {:error, res} = err -> Logger.info("Failed to complete negotiation for #{inspect(pc)}") terminate_pc(pc) err @@ -110,7 +110,7 @@ defmodule WhipWhep.PeerSupervisor do receive do {:ex_webrtc, ^pc, {:ice_gathering_state_change, :complete}} -> :ok after - 1000 -> {:error, :timeout} + 1000 -> :ok end end diff --git a/examples/whip_whep/priv/static/script.js b/examples/whip_whep/priv/static/script.js index 3e39a771..a5de8fe9 100644 --- a/examples/whip_whep/priv/static/script.js +++ b/examples/whip_whep/priv/static/script.js @@ -22,6 +22,8 @@ async function sendCandidate(candidate) { async function connect() { const pc = new RTCPeerConnection(pcConfig); + // expose pc for easier debugging and experiments + window.pc = pc; pc.ontrack = event => videoPlayer.srcObject = event.streams[0]; pc.onicegatheringstatechange = () => console.log("Gathering state change: " + pc.iceGatheringState); diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 8c84a974..b93e5a93 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -251,12 +251,12 @@ defmodule ExWebRTC.DTLSTransport do @impl true def handle_cast({:send_rtp, _data}, state) do - Logger.warning("Attempted to send RTP before DTLS handshake has been finished. Ignoring.") + Logger.debug("Attempted to send RTP in wrong DTLS state: #{state.dtls_state}. Ignoring.") {:noreply, state} end @impl true - def handle_cast({:send_rtcp, data}, state) do + def handle_cast({:send_rtcp, data}, %{dtls_state: :connected, ice_connected: true} = state) do case ExLibSRTP.protect_rtcp(state.out_srtp, data) do {:ok, protected} -> do_send(state, protected) {:error, reason} -> Logger.warning("Unable to protect RTCP: #{inspect(reason)}") @@ -266,7 +266,13 @@ defmodule ExWebRTC.DTLSTransport do end @impl true - def handle_cast({:send_data, data}, state) do + def handle_cast({:send_rtcp, _data}, state) do + Logger.debug("Attempted to send RTCP in wrong DTLS state: #{state.dtls_state}. Ignoring.") + {:noreply, state} + end + + @impl true + def handle_cast({:send_data, data}, %{dtls_state: :connected, ice_connected: true} = state) do case ExDTLS.write_data(state.dtls, data) do {:ok, protected} -> do_send(state, protected) {:error, reason} -> Logger.warning("Unable to protect data: #{inspect(reason)}") @@ -275,6 +281,12 @@ defmodule ExWebRTC.DTLSTransport do {:noreply, state} end + @impl true + def handle_cast({:send_data, _data}, state) do + Logger.debug("Attempted to send data in wrong DTLS state: #{state.dtls_state}. Ignoring.") + {:noreply, state} + end + @impl true def handle_cast({:set_packet_loss, value}, state) do state = %{state | packet_loss: value} @@ -312,9 +324,13 @@ defmodule ExWebRTC.DTLSTransport do @impl true def handle_info({:ex_ice, _from, {:data, _data} = msg}, state) do case handle_ice_data(msg, state) do - {:ok, state} -> {:noreply, state} - # we use shutdown to avoid logging an error - {:error, reason} -> {:stop, {:shutdown, reason}, state} + {:ok, state} -> + {:noreply, state} + + {:error, _reason} -> + # See W3C WebRTC sec. 5.5. + state = update_dtls_state(state, :failed) + {:noreply, state} end end diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 1cbc14bc..a4d2ac5b 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -409,7 +409,7 @@ defmodule ExWebRTC.PeerConnection do For more information, refer to the [RTCPeerConnection: createOffer() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer). """ - @spec create_offer(peer_connection(), restart_ice?: boolean()) :: + @spec create_offer(peer_connection(), ice_restart: boolean()) :: {:ok, SessionDescription.t()} | {:error, term()} def create_offer(peer_connection, options \\ []) do GenServer.call(peer_connection, {:create_offer, options}) @@ -1186,7 +1186,8 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_cast({:send_rtp, track_id, packet, opts}, state) do + def handle_cast({:send_rtp, track_id, packet, opts}, %{conn_state: conn_state} = state) + when conn_state != :failed do rtx? = Keyword.get(opts, :rtx?, false) # TODO: iterating over transceivers is not optimal @@ -1238,7 +1239,13 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_cast({:send_pli, track_id, rid}, state) do + def handle_cast({:send_rtp, _track_id, _packet, _opts}, state) do + {:noreply, state} + end + + @impl true + def handle_cast({:send_pli, track_id, rid}, %{conn_state: conn_state} = state) + when conn_state != :failed do state.transceivers |> Enum.with_index() |> Enum.find(fn {tr, _idx} -> tr.receiver.track.id == track_id end) @@ -1265,7 +1272,13 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_cast({:send_data, channel_ref, data_type, data}, state) do + def handle_cast({:send_pli, _track_id, _rid}, state) do + {:noreply, state} + end + + @impl true + def handle_cast({:send_data, channel_ref, data_type, data}, %{conn_state: conn_state} = state) + when conn_state != :failed do {events, sctp_transport} = SCTPTransport.send(state.sctp_transport, channel_ref, data_type, data) @@ -1274,6 +1287,11 @@ defmodule ExWebRTC.PeerConnection do {:noreply, %{state | sctp_transport: sctp_transport}} end + @impl true + def handle_cast({:send_data, _channel_ref, _data_type, _data}, state) do + {:noreply, state} + end + @impl true def handle_cast({:set_packet_loss, packet_loss}, state) do DTLSTransport.set_packet_loss(state.dtls_transport, packet_loss) @@ -1283,6 +1301,7 @@ defmodule ExWebRTC.PeerConnection do @impl true def handle_info({:ex_ice, _from, {:connection_state_change, new_ice_state}}, state) do state = %{state | ice_state: new_ice_state} + notify(state.owner, {:ice_connection_state_change, new_ice_state}) next_conn_state = next_conn_state(new_ice_state, state.dtls_state) state = update_conn_state(state, next_conn_state) @@ -1290,14 +1309,7 @@ defmodule ExWebRTC.PeerConnection do :ok = DTLSTransport.set_ice_connected(state.dtls_transport) end - notify(state.owner, {:ice_connection_state_change, new_ice_state}) - - if next_conn_state == :failed do - Logger.debug("Stopping PeerConnection") - {:stop, {:shutdown, :conn_state_failed}, state} - else - {:noreply, state} - end + {:noreply, state} end @impl true