├── .clang-format ├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── wasm ├── include └── rtc │ ├── candidate.hpp │ ├── channel.hpp │ ├── common.hpp │ ├── configuration.hpp │ ├── datachannel.hpp │ ├── description.hpp │ ├── global.hpp │ ├── peerconnection.hpp │ ├── reliability.hpp │ ├── rtc.hpp │ └── websocket.hpp ├── js ├── webrtc.js └── websocket.js └── src ├── candidate.cpp ├── channel.cpp ├── configuration.cpp ├── datachannel.cpp ├── description.cpp ├── global.cpp ├── peerconnection.cpp └── websocket.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | TabWidth: 4 5 | UseTab: ForIndentation 6 | --- 7 | Language: Cpp 8 | Standard: Cpp11 9 | AccessModifierOffset: -4 10 | ColumnLimit: 100 11 | --- 12 | Language: JavaScript 13 | IndentWidth: 2 14 | UseTab: Never 15 | ColumnLimit: 100 16 | ... 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = tab 10 | indent_size = 4 11 | 12 | [*.js] 13 | insert_final_newline = true 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.py] 18 | insert_final_newline = false 19 | indent_style = space 20 | indent_size = 4 21 | 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ['paullouisageneau'] 2 | custom: ['https://paypal.me/paullouisageneau'] 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.d 3 | *.o 4 | *.a 5 | *.so 6 | compile_commands.json 7 | tests 8 | .DS_Store 9 | 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(datachannel-wasm 3 | DESCRIPTION "C++ WebRTC Data Channels for WebAssembly in browsers" 4 | VERSION 0.3.2 5 | LANGUAGES CXX) 6 | 7 | if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten") 8 | message(FATAL_ERROR "datachannel-wasm must be compiled with Emscripten.") 9 | endif() 10 | 11 | set(WASM_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/wasm/src) 12 | set(DATACHANNELS_SRC 13 | ${WASM_SRC_DIR}/candidate.cpp 14 | ${WASM_SRC_DIR}/channel.cpp 15 | ${WASM_SRC_DIR}/configuration.cpp 16 | ${WASM_SRC_DIR}/description.cpp 17 | ${WASM_SRC_DIR}/datachannel.cpp 18 | ${WASM_SRC_DIR}/global.cpp 19 | ${WASM_SRC_DIR}/peerconnection.cpp 20 | ${WASM_SRC_DIR}/websocket.cpp) 21 | 22 | add_library(datachannel-wasm STATIC ${DATACHANNELS_SRC}) 23 | set_target_properties(datachannel-wasm PROPERTIES 24 | VERSION ${PROJECT_VERSION} 25 | CXX_STANDARD 17) 26 | 27 | target_include_directories(datachannel-wasm PUBLIC 28 | ${CMAKE_CURRENT_SOURCE_DIR}/wasm/include) 29 | target_include_directories(datachannel-wasm PRIVATE 30 | ${CMAKE_CURRENT_SOURCE_DIR}/wasm/include/rtc) 31 | 32 | target_link_options(datachannel-wasm PUBLIC 33 | "SHELL:--js-library \"${CMAKE_CURRENT_SOURCE_DIR}/wasm/js/webrtc.js\"" 34 | "SHELL:--js-library \"${CMAKE_CURRENT_SOURCE_DIR}/wasm/js/websocket.js\"") 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2022 Paul-Louis Ageneau and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # datachannel-wasm - C++ WebRTC Data Channels for WebAssembly in browsers 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Gitter](https://badges.gitter.im/libdatachannel/datachannel-wasm.svg)](https://gitter.im/libdatachannel/datachannel-wasm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord](https://img.shields.io/discord/903257095539925006?logo=discord)](https://discord.gg/jXAP8jp3Nn) 4 | 5 | datachannel-wasm is a C++ WebRTC Data Channels and WebSocket wrapper for [Emscripten](https://emscripten.org/) compatible with [libdatachannel](https://github.com/paullouisageneau/libdatachannel). 6 | 7 | datachannel-wasm exposes the same API as [libdatachannel](https://github.com/paullouisageneau/libdatachannel), and therefore allows to compile the same C++ code using Data Channels and WebSockets to WebAssembly for browsers in addition to native targets supported by libdatachannel. The interface is only a subset of the one of [libdatachannel](https://github.com/paullouisageneau/libdatachannel), in particular, tracks and media transport are not supported. See what is available in [wasm/include](https://github.com/paullouisageneau/datachannel-wasm/tree/master/wasm/include/rtc). 8 | 9 | These wrappers were originally written for my multiplayer game [Convergence](https://github.com/paullouisageneau/convergence) and were extracted from there to be easily reusable. 10 | 11 | datachannel-wasm is licensed under MIT, see [LICENSE](https://github.com/paullouisageneau/datachannel-wasm/blob/master/LICENSE). 12 | 13 | ## Installation 14 | 15 | You just need to add datachannel-wasm as a submodule in your Emscripten project: 16 | ```bash 17 | $ git submodule add https://github.com/paullouisageneau/datachannel-wasm.git deps/datachannel-wasm 18 | $ git submodule update --init --recursive 19 | ``` 20 | 21 | CMakeLists.txt: 22 | ```cmake 23 | [...] 24 | add_subdirectory(deps/datachannel-wasm EXCLUDE_FROM_ALL) 25 | target_link_libraries(YOUR_TARGET datachannel-wasm) 26 | ``` 27 | 28 | Since datachannel-wasm is compatible with [libdatachannel](https://github.com/paullouisageneau/libdatachannel), you can easily leverage both to make the same C++ code compile to native (including Apple macOS and Microsoft Windows): 29 | 30 | ```bash 31 | $ git submodule add https://github.com/paullouisageneau/datachannel-wasm.git deps/datachannel-wasm 32 | $ git submodule add https://github.com/paullouisageneau/libdatachannel.git deps/libdatachannel 33 | $ git submodule update --init --recursive --depth 1 34 | ``` 35 | 36 | CMakeLists.txt: 37 | ```cmake 38 | if(CMAKE_SYSTEM_NAME MATCHES "Emscripten") 39 | add_subdirectory(deps/datachannel-wasm EXCLUDE_FROM_ALL) 40 | target_link_libraries(YOUR_TARGET datachannel-wasm) 41 | else() 42 | option(NO_MEDIA "Disable media support in libdatachannel" ON) 43 | add_subdirectory(deps/libdatachannel EXCLUDE_FROM_ALL) 44 | target_link_libraries(YOUR_TARGET datachannel) 45 | endif() 46 | ``` 47 | 48 | ## Building 49 | 50 | Building requires that you have [emsdk](https://github.com/emscripten-core/emsdk) installed and activated in your environment: 51 | ```bash 52 | $ cmake -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake 53 | $ cd build 54 | $ make -j2 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /wasm/include/rtc/candidate.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_CANDIDATE_H 24 | #define RTC_CANDIDATE_H 25 | 26 | #include "common.hpp" 27 | 28 | namespace rtc { 29 | 30 | class Candidate { 31 | public: 32 | Candidate(const string &candidate, const string &mid); 33 | string candidate() const; 34 | string mid() const; 35 | operator string() const; 36 | 37 | private: 38 | string mCandidate; 39 | string mMid; 40 | }; 41 | 42 | } // namespace rtc 43 | 44 | std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate); 45 | 46 | #endif // RTC_CANDIDATE_H 47 | -------------------------------------------------------------------------------- /wasm/include/rtc/channel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_CHANNEL_H 24 | #define RTC_CHANNEL_H 25 | 26 | #include "common.hpp" 27 | 28 | #include 29 | 30 | namespace rtc { 31 | 32 | class Channel { 33 | public: 34 | virtual ~Channel() = default; 35 | 36 | virtual void close() = 0; 37 | virtual bool send(message_variant data) = 0; 38 | virtual bool send(const byte *data, size_t size) = 0; 39 | 40 | virtual bool isOpen() const = 0; 41 | virtual bool isClosed() const = 0; 42 | virtual size_t bufferedAmount() const; 43 | 44 | void onOpen(std::function callback); 45 | void onClosed(std::function callback); 46 | void onError(std::function callback); 47 | void onMessage(std::function callback); 48 | void onMessage(std::function binaryCallback, 49 | std::function stringCallback); 50 | void onBufferedAmountLow(std::function callback); 51 | 52 | virtual void setBufferedAmountLowThreshold(size_t amount); 53 | 54 | protected: 55 | virtual void triggerOpen(); 56 | virtual void triggerClosed(); 57 | virtual void triggerError(string error); 58 | virtual void triggerMessage(message_variant data); 59 | virtual void triggerBufferedAmountLow(); 60 | 61 | private: 62 | std::function mOpenCallback; 63 | std::function mClosedCallback; 64 | std::function mErrorCallback; 65 | std::function mMessageCallback; 66 | std::function mBufferedAmountLowCallback; 67 | }; 68 | 69 | } // namespace rtc 70 | 71 | #endif // RTC_CHANNEL_H 72 | -------------------------------------------------------------------------------- /wasm/include/rtc/common.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_COMMON_H 24 | #define RTC_COMMON_H 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace rtc { 37 | 38 | using std::byte; 39 | using std::nullopt; 40 | using std::optional; 41 | using std::shared_ptr; 42 | using std::string; 43 | using std::unique_ptr; 44 | using std::variant; 45 | using std::weak_ptr; 46 | 47 | using binary = std::vector; 48 | using message_variant = std::variant; 49 | 50 | using std::size_t; 51 | using std::uint16_t; 52 | using std::uint32_t; 53 | using std::uint64_t; 54 | using std::uint8_t; 55 | 56 | template struct overloaded : Ts... { 57 | using Ts::operator()...; 58 | }; 59 | template overloaded(Ts...) -> overloaded; 60 | 61 | } // namespace rtc 62 | 63 | #endif // RTC_COMMON_H 64 | -------------------------------------------------------------------------------- /wasm/include/rtc/configuration.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_CONFIGURATION_H 24 | #define RTC_CONFIGURATION_H 25 | 26 | #include "common.hpp" 27 | 28 | #include 29 | 30 | namespace rtc { 31 | 32 | struct IceServer { 33 | enum class Type : int { Stun = 0, Turn, Dummy }; 34 | enum class RelayType : int { TurnUdp = 0, TurnTcp, TurnTls }; 35 | 36 | // Note: Contrary to libdatachannel, the URL constructor does not parse the URL. 37 | // Instead, it creates a Dummy IceServer to pass the URL as-is to the browser. 38 | IceServer(const string &url); 39 | 40 | // STUN 41 | IceServer(string hostname_, uint16_t port_); 42 | IceServer(string hostname_, string service_); 43 | 44 | // TURN 45 | IceServer(string hostname_, uint16_t port, string username_, string password_, 46 | RelayType relayType_ = RelayType::TurnUdp); 47 | IceServer(string hostname_, string service_, string username_, string password_, 48 | RelayType relayType_ = RelayType::TurnUdp); 49 | 50 | string hostname; 51 | uint16_t port; 52 | Type type; 53 | string username; 54 | string password; 55 | RelayType relayType; 56 | }; 57 | 58 | struct Configuration { 59 | std::vector iceServers; 60 | }; 61 | 62 | } // namespace rtc 63 | 64 | #endif // RTC_CONFIGURATION_H 65 | -------------------------------------------------------------------------------- /wasm/include/rtc/datachannel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_DATACHANNEL_H 24 | #define RTC_DATACHANNEL_H 25 | 26 | #include "channel.hpp" 27 | #include "common.hpp" 28 | #include "reliability.hpp" 29 | 30 | namespace rtc { 31 | 32 | class DataChannel final : public Channel { 33 | public: 34 | explicit DataChannel(int id); 35 | ~DataChannel(); 36 | 37 | void close() override; 38 | bool send(message_variant data) override; 39 | bool send(const byte *data, size_t size) override; 40 | 41 | bool isOpen() const override; 42 | bool isClosed() const override; 43 | size_t bufferedAmount() const override; 44 | string label() const; 45 | Reliability reliability() const; 46 | 47 | void setBufferedAmountLowThreshold(size_t amount) override; 48 | 49 | private: 50 | void triggerOpen() override; 51 | 52 | int mId; 53 | string mLabel; 54 | bool mConnected; 55 | 56 | static void OpenCallback(void *ptr); 57 | static void ErrorCallback(const char *error, void *ptr); 58 | static void MessageCallback(const char *data, int size, void *ptr); 59 | static void BufferedAmountLowCallback(void *ptr); 60 | }; 61 | 62 | } // namespace rtc 63 | 64 | #endif // RTC_DATACHANNEL_H 65 | -------------------------------------------------------------------------------- /wasm/include/rtc/description.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_DESCRIPTION_H 24 | #define RTC_DESCRIPTION_H 25 | 26 | #include "common.hpp" 27 | 28 | namespace rtc { 29 | 30 | class Description { 31 | public: 32 | enum class Type { Unspec, Offer, Answer, Pranswer, Rollback }; 33 | 34 | Description(const string &sdp, Type type); 35 | Description(const string &sdp, string typeString); 36 | 37 | Type type() const; 38 | string typeString() const; 39 | 40 | operator string() const; 41 | 42 | static Type stringToType(const string &typeString); 43 | static string typeToString(Type type); 44 | 45 | private: 46 | string mSdp; 47 | string mType; 48 | }; 49 | 50 | } // namespace rtc 51 | 52 | std::ostream &operator<<(std::ostream &out, const rtc::Description &description); 53 | std::ostream &operator<<(std::ostream &out, rtc::Description::Type type); 54 | 55 | #endif // RTC_DESCRIPTION_H 56 | -------------------------------------------------------------------------------- /wasm/include/rtc/global.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2024 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_GLOBAL_H 24 | #define RTC_GLOBAL_H 25 | 26 | #include "common.hpp" 27 | 28 | #include 29 | #include 30 | 31 | namespace rtc { 32 | 33 | enum class LogLevel { 34 | None = 0, 35 | Fatal = 1, 36 | Error = 2, 37 | Warning = 3, 38 | Info = 4, 39 | Debug = 5, 40 | Verbose = 6 41 | }; 42 | 43 | typedef std::function LogCallback; 44 | 45 | // Dummy function for compatibility with libdatachannel 46 | void InitLogger(LogLevel level, LogCallback callback = nullptr); 47 | void Preload(); 48 | std::shared_future Cleanup(); 49 | 50 | std::ostream &operator<<(std::ostream &out, LogLevel level); 51 | 52 | } // namespace rtc 53 | 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /wasm/include/rtc/peerconnection.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_PEERCONNECTION_H 24 | #define RTC_PEERCONNECTION_H 25 | 26 | #include "candidate.hpp" 27 | #include "common.hpp" 28 | #include "configuration.hpp" 29 | #include "datachannel.hpp" 30 | #include "description.hpp" 31 | #include "reliability.hpp" 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | namespace rtc { 38 | 39 | struct DataChannelInit { 40 | Reliability reliability = {}; 41 | }; 42 | 43 | class PeerConnection final { 44 | public: 45 | enum class State : int { 46 | New = 0, 47 | Connecting = 1, 48 | Connected = 2, 49 | Disconnected = 3, 50 | Failed = 4, 51 | Closed = 5 52 | }; 53 | 54 | enum class IceState : int { 55 | New = 0, 56 | Checking = 1, 57 | Connected = 2, 58 | Completed = 3, 59 | Failed = 4, 60 | Disconnected = 5, 61 | Closed = 6 62 | }; 63 | 64 | enum class GatheringState : int { New = 0, InProgress = 1, Complete = 2 }; 65 | 66 | enum class SignalingState : int { 67 | Stable = 0, 68 | HaveLocalOffer = 1, 69 | HaveRemoteOffer = 2, 70 | HaveLocalPranswer = 3, 71 | HaveRemotePranswer = 4, 72 | }; 73 | 74 | PeerConnection(); 75 | PeerConnection(const Configuration &config); 76 | ~PeerConnection(); 77 | 78 | void close(); 79 | 80 | State state() const; 81 | IceState iceState() const; 82 | GatheringState gatheringState() const; 83 | SignalingState signalingState() const; 84 | optional localDescription() const; 85 | optional remoteDescription() const; 86 | 87 | shared_ptr createDataChannel(const string &label, DataChannelInit init = {}); 88 | 89 | void setRemoteDescription(const Description &description); 90 | void addRemoteCandidate(const Candidate &candidate); 91 | 92 | void onDataChannel(std::function)> callback); 93 | void onLocalDescription(std::function callback); 94 | void onLocalCandidate(std::function callback); 95 | void onStateChange(std::function callback); 96 | void onIceStateChange(std::function callback); 97 | void onGatheringStateChange(std::function callback); 98 | void onSignalingStateChange(std::function callback); 99 | 100 | protected: 101 | void triggerDataChannel(shared_ptr dataChannel); 102 | void triggerLocalDescription(const Description &description); 103 | void triggerLocalCandidate(const Candidate &candidate); 104 | void triggerStateChange(State state); 105 | void triggerIceStateChange(IceState state); 106 | void triggerGatheringStateChange(GatheringState state); 107 | void triggerSignalingStateChange(SignalingState state); 108 | 109 | std::function)> mDataChannelCallback; 110 | std::function mLocalDescriptionCallback; 111 | std::function mLocalCandidateCallback; 112 | std::function mStateChangeCallback; 113 | std::function mIceStateChangeCallback; 114 | std::function mGatheringStateChangeCallback; 115 | std::function mSignalingStateChangeCallback; 116 | 117 | private: 118 | int mId; 119 | State mState = State::New; 120 | IceState mIceState = IceState::New; 121 | GatheringState mGatheringState = GatheringState::New; 122 | SignalingState mSignalingState = SignalingState::Stable; 123 | 124 | static void DataChannelCallback(int dc, void *ptr); 125 | static void DescriptionCallback(const char *sdp, const char *type, void *ptr); 126 | static void CandidateCallback(const char *candidate, const char *mid, void *ptr); 127 | static void StateChangeCallback(int state, void *ptr); 128 | static void IceStateChangeCallback(int state, void *ptr); 129 | static void GatheringStateChangeCallback(int state, void *ptr); 130 | static void SignalingStateChangeCallback(int state, void *ptr); 131 | }; 132 | 133 | std::ostream &operator<<(std::ostream &out, PeerConnection::State state); 134 | std::ostream &operator<<(std::ostream &out, PeerConnection::IceState state); 135 | std::ostream &operator<<(std::ostream &out, PeerConnection::GatheringState state); 136 | std::ostream &operator<<(std::ostream &out, PeerConnection::SignalingState state); 137 | 138 | } // namespace rtc 139 | 140 | #endif // RTC_PEERCONNECTION_H 141 | -------------------------------------------------------------------------------- /wasm/include/rtc/reliability.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_RELIABILITY_H 24 | #define RTC_RELIABILITY_H 25 | 26 | #include "common.hpp" 27 | 28 | #include 29 | 30 | namespace rtc { 31 | 32 | struct Reliability { 33 | // It true, the channel does not enforce message ordering and out-of-order delivery is allowed 34 | bool unordered = false; 35 | 36 | // If both maxPacketLifeTime or maxRetransmits are unset, the channel is reliable. 37 | // If either maxPacketLifeTime or maxRetransmits is set, the channel is unreliable. 38 | // (The settings are exclusive so both maxPacketLifetime and maxRetransmits must not be set.) 39 | 40 | // Time window during which transmissions and retransmissions may occur 41 | optional maxPacketLifeTime; 42 | 43 | // Maximum number of retransmissions that are attempted 44 | optional maxRetransmits; 45 | }; 46 | 47 | } // namespace rtc 48 | 49 | #endif // RTC_RELIABILITY_H 50 | -------------------------------------------------------------------------------- /wasm/include/rtc/rtc.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_H 24 | #define RTC_H 25 | 26 | #include "common.hpp" 27 | #include "global.hpp" 28 | 29 | #include "datachannel.hpp" 30 | #include "peerconnection.hpp" 31 | #include "websocket.hpp" 32 | 33 | #endif // RTC_H 34 | -------------------------------------------------------------------------------- /wasm/include/rtc/websocket.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef RTC_WEBSOCKET_H 24 | #define RTC_WEBSOCKET_H 25 | 26 | #include "channel.hpp" 27 | #include "common.hpp" 28 | 29 | namespace rtc { 30 | 31 | // WebSocket wrapper for emscripten 32 | class WebSocket final : public Channel { 33 | public: 34 | enum class State : int { 35 | Connecting = 0, 36 | Open = 1, 37 | Closing = 2, 38 | Closed = 3, 39 | }; 40 | 41 | WebSocket(); 42 | ~WebSocket(); 43 | 44 | void open(const string &url); 45 | void close() override; 46 | bool send(message_variant data) override; 47 | bool send(const byte *data, size_t size) override; 48 | 49 | State readyState() const; 50 | 51 | bool isOpen() const override; 52 | bool isClosed() const override; 53 | 54 | optional url() const; 55 | 56 | private: 57 | void triggerOpen() override; 58 | 59 | int mId; 60 | bool mConnected; 61 | 62 | static void OpenCallback(void *ptr); 63 | static void ErrorCallback(const char *error, void *ptr); 64 | static void MessageCallback(const char *data, int size, void *ptr); 65 | }; 66 | 67 | std::ostream &operator<<(std::ostream &out, WebSocket::State state); 68 | 69 | } // namespace rtc 70 | 71 | #endif // RTC_WEBSOCKET_H 72 | -------------------------------------------------------------------------------- /wasm/js/webrtc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | (function() { 24 | var WebRTC = { 25 | $WEBRTC: { 26 | peerConnectionsMap: {}, 27 | dataChannelsMap: {}, 28 | nextId: 1, 29 | 30 | allocUTF8FromString: function(str) { 31 | var strLen = lengthBytesUTF8(str); 32 | var strOnHeap = _malloc(strLen+1); 33 | stringToUTF8(str, strOnHeap, strLen+1); 34 | return strOnHeap; 35 | }, 36 | 37 | registerPeerConnection: function(peerConnection) { 38 | var pc = WEBRTC.nextId++; 39 | WEBRTC.peerConnectionsMap[pc] = peerConnection; 40 | peerConnection.onnegotiationneeded = function() { 41 | peerConnection.createOffer() 42 | .then(function(offer) { 43 | return WEBRTC.handleDescription(peerConnection, offer); 44 | }) 45 | .catch(function(err) { 46 | console.error(err); 47 | }); 48 | }; 49 | peerConnection.onicecandidate = function(evt) { 50 | if(evt.candidate && evt.candidate.candidate) 51 | WEBRTC.handleCandidate(peerConnection, evt.candidate); 52 | }; 53 | peerConnection.onconnectionstatechange = function() { 54 | WEBRTC.handleConnectionStateChange(peerConnection, peerConnection.connectionState) 55 | }; 56 | peerConnection.oniceconnectionstatechange = function() { 57 | WEBRTC.handleIceStateChange(peerConnection, peerConnection.iceConnectionState) 58 | }; 59 | peerConnection.onicegatheringstatechange = function() { 60 | WEBRTC.handleGatheringStateChange(peerConnection, peerConnection.iceGatheringState) 61 | }; 62 | peerConnection.onsignalingstatechange = function() { 63 | WEBRTC.handleSignalingStateChange(peerConnection, peerConnection.signalingState) 64 | }; 65 | return pc; 66 | }, 67 | 68 | registerDataChannel: function(dataChannel) { 69 | var dc = WEBRTC.nextId++; 70 | WEBRTC.dataChannelsMap[dc] = dataChannel; 71 | dataChannel.binaryType = 'arraybuffer'; 72 | return dc; 73 | }, 74 | 75 | handleDescription: function(peerConnection, description) { 76 | return peerConnection.setLocalDescription(description) 77 | .then(function() { 78 | if(peerConnection.rtcUserDeleted) return; 79 | if(!peerConnection.rtcDescriptionCallback) return; 80 | var desc = peerConnection.localDescription; 81 | var pSdp = WEBRTC.allocUTF8FromString(desc.sdp); 82 | var pType = WEBRTC.allocUTF8FromString(desc.type); 83 | var callback = peerConnection.rtcDescriptionCallback; 84 | var userPointer = peerConnection.rtcUserPointer || 0; 85 | {{{ makeDynCall('viii', 'callback') }}} (pSdp, pType, userPointer); 86 | _free(pSdp); 87 | _free(pType); 88 | }); 89 | }, 90 | 91 | handleCandidate: function(peerConnection, candidate) { 92 | if(peerConnection.rtcUserDeleted) return; 93 | if(!peerConnection.rtcCandidateCallback) return; 94 | var pCandidate = WEBRTC.allocUTF8FromString(candidate.candidate); 95 | var pSdpMid = WEBRTC.allocUTF8FromString(candidate.sdpMid); 96 | var candidateCallback = peerConnection.rtcCandidateCallback; 97 | var userPointer = peerConnection.rtcUserPointer || 0; 98 | {{{ makeDynCall('viii', 'candidateCallback') }}} (pCandidate, pSdpMid, userPointer); 99 | _free(pCandidate); 100 | _free(pSdpMid); 101 | }, 102 | 103 | handleConnectionStateChange: function(peerConnection, connectionState) { 104 | if(peerConnection.rtcUserDeleted) return; 105 | if(!peerConnection.rtcStateChangeCallback) return; 106 | var map = { 107 | 'new': 0, 108 | 'connecting': 1, 109 | 'connected': 2, 110 | 'disconnected': 3, 111 | 'failed': 4, 112 | 'closed': 5, 113 | }; 114 | if(connectionState in map) { 115 | var stateChangeCallback = peerConnection.rtcStateChangeCallback; 116 | var userPointer = peerConnection.rtcUserPointer || 0; 117 | {{{ makeDynCall('vii', 'stateChangeCallback') }}} (map[connectionState], userPointer); 118 | } 119 | }, 120 | 121 | handleIceStateChange: function(peerConnection, iceConnectionState) { 122 | if(peerConnection.rtcUserDeleted) return; 123 | if(!peerConnection.rtcIceStateChangeCallback) return; 124 | var map = { 125 | 'new': 0, 126 | 'checking': 1, 127 | 'connected': 2, 128 | 'completed': 3, 129 | 'failed': 4, 130 | 'disconnected': 5, 131 | 'closed': 6, 132 | }; 133 | if(iceConnectionState in map) { 134 | var iceStateChangeCallback = peerConnection.rtcIceStateChangeCallback; 135 | var userPointer = peerConnection.rtcUserPointer || 0; 136 | {{{ makeDynCall('vii', 'iceStateChangeCallback') }}} (map[iceConnectionState], userPointer); 137 | } 138 | }, 139 | 140 | handleGatheringStateChange: function(peerConnection, iceGatheringState) { 141 | if(peerConnection.rtcUserDeleted) return; 142 | if(!peerConnection.rtcGatheringStateChangeCallback) return; 143 | var map = { 144 | 'new': 0, 145 | 'gathering': 1, 146 | 'complete': 2, 147 | }; 148 | if(iceGatheringState in map) { 149 | var gatheringStateChangeCallback = peerConnection.rtcGatheringStateChangeCallback; 150 | var userPointer = peerConnection.rtcUserPointer || 0; 151 | {{{ makeDynCall('vii', 'gatheringStateChangeCallback') }}} (map[iceGatheringState], userPointer); 152 | } 153 | }, 154 | 155 | handleSignalingStateChange: function(peerConnection, signalingState) { 156 | if(peerConnection.rtcUserDeleted) return; 157 | if(!peerConnection.rtcSignalingStateChangeCallback) return; 158 | var map = { 159 | 'stable': 0, 160 | 'have-local-offer': 1, 161 | 'have-remote-offer': 2, 162 | 'have-local-pranswer': 3, 163 | 'have-remote-pranswer': 4, 164 | }; 165 | if(signalingState in map) { 166 | var signalingStateChangeCallback = peerConnection.rtcSignalingStateChangeCallback; 167 | var userPointer = peerConnection.rtcUserPointer || 0; 168 | {{{ makeDynCall('vii', 'signalingStateChangeCallback') }}} (map[signalingState], userPointer); 169 | } 170 | }, 171 | }, 172 | 173 | rtcCreatePeerConnection: function(pUrls, pUsernames, pPasswords, nIceServers) { 174 | if(!window.RTCPeerConnection) return 0; 175 | var iceServers = []; 176 | for(var i = 0; i < nIceServers; ++i) { 177 | var heap = Module['HEAPU32']; 178 | var pUrl = heap[pUrls/heap.BYTES_PER_ELEMENT + i]; 179 | var url = UTF8ToString(pUrl); 180 | var pUsername = heap[pUsernames/heap.BYTES_PER_ELEMENT + i]; 181 | var username = UTF8ToString(pUsername); 182 | var pPassword = heap[pPasswords/heap.BYTES_PER_ELEMENT + i]; 183 | var password = UTF8ToString(pPassword); 184 | if (username == "") { 185 | iceServers.push({ 186 | urls: [url], 187 | }); 188 | } else { 189 | iceServers.push({ 190 | urls: [url], 191 | username: username, 192 | credential: password 193 | }); 194 | } 195 | } 196 | var config = { 197 | iceServers: iceServers, 198 | }; 199 | return WEBRTC.registerPeerConnection(new RTCPeerConnection(config)); 200 | }, 201 | 202 | rtcDeletePeerConnection: function(pc) { 203 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 204 | if(peerConnection) { 205 | peerConnection.close(); 206 | peerConnection.rtcUserDeleted = true; 207 | delete WEBRTC.peerConnectionsMap[pc]; 208 | } 209 | }, 210 | 211 | rtcGetLocalDescription: function(pc) { 212 | if(!pc) return 0; 213 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 214 | var localDescription = peerConnection.localDescription; 215 | if(!localDescription) return 0; 216 | var sdp = WEBRTC.allocUTF8FromString(localDescription.sdp); 217 | // sdp should be freed later in c++. 218 | return sdp; 219 | }, 220 | 221 | rtcGetLocalDescriptionType: function(pc) { 222 | if(!pc) return 0; 223 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 224 | var localDescription = peerConnection.localDescription; 225 | if(!localDescription) return 0; 226 | var type = WEBRTC.allocUTF8FromString(localDescription.type); 227 | // type should be freed later in c++. 228 | return type; 229 | }, 230 | 231 | rtcGetRemoteDescription: function(pc) { 232 | if(!pc) return 0; 233 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 234 | var remoteDescription = peerConnection.remoteDescription; 235 | if(!remoteDescription) return 0; 236 | var sdp = WEBRTC.allocUTF8FromString(remoteDescription.sdp); 237 | // sdp should be freed later in c++. 238 | return sdp; 239 | }, 240 | 241 | rtcGetRemoteDescriptionType: function(pc) { 242 | if(!pc) return 0; 243 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 244 | var remoteDescription = peerConnection.remoteDescription; 245 | if(!remoteDescription) return 0; 246 | var type = WEBRTC.allocUTF8FromString(remoteDescription.type); 247 | // type should be freed later in c++. 248 | return type; 249 | }, 250 | 251 | rtcCreateDataChannel: function(pc, pLabel, unordered, maxRetransmits, maxPacketLifeTime) { 252 | if(!pc) return 0; 253 | var label = UTF8ToString(pLabel); 254 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 255 | var datachannelInit = { 256 | ordered: !unordered, 257 | }; 258 | 259 | // Browsers throw an exception when both are present (even if set to null) 260 | if (maxRetransmits >= 0) datachannelInit.maxRetransmits = maxRetransmits; 261 | else if (maxPacketLifeTime >= 0) datachannelInit.maxPacketLifeTime = maxPacketLifeTime; 262 | 263 | var channel = peerConnection.createDataChannel(label, datachannelInit); 264 | return WEBRTC.registerDataChannel(channel); 265 | }, 266 | 267 | rtcDeleteDataChannel: function(dc) { 268 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 269 | if(dataChannel) { 270 | dataChannel.rtcUserDeleted = true; 271 | delete WEBRTC.dataChannelsMap[dc]; 272 | } 273 | }, 274 | 275 | rtcSetDataChannelCallback: function(pc, dataChannelCallback) { 276 | if(!pc) return; 277 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 278 | peerConnection.ondatachannel = function(evt) { 279 | if(peerConnection.rtcUserDeleted) return; 280 | var dc = WEBRTC.registerDataChannel(evt.channel); 281 | var userPointer = peerConnection.rtcUserPointer || 0; 282 | {{{ makeDynCall('vii', 'dataChannelCallback') }}} (dc, userPointer); 283 | }; 284 | }, 285 | 286 | rtcSetLocalDescriptionCallback: function(pc, descriptionCallback) { 287 | if(!pc) return; 288 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 289 | peerConnection.rtcDescriptionCallback = descriptionCallback; 290 | }, 291 | 292 | rtcSetLocalCandidateCallback: function(pc, candidateCallback) { 293 | if(!pc) return; 294 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 295 | peerConnection.rtcCandidateCallback = candidateCallback; 296 | }, 297 | 298 | rtcSetStateChangeCallback: function(pc, stateChangeCallback) { 299 | if(!pc) return; 300 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 301 | peerConnection.rtcStateChangeCallback = stateChangeCallback; 302 | }, 303 | 304 | rtcSetIceStateChangeCallback: function(pc, iceStateChangeCallback) { 305 | if(!pc) return; 306 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 307 | peerConnection.rtcIceStateChangeCallback = iceStateChangeCallback; 308 | }, 309 | 310 | rtcSetGatheringStateChangeCallback: function(pc, gatheringStateChangeCallback) { 311 | if(!pc) return; 312 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 313 | peerConnection.rtcGatheringStateChangeCallback = gatheringStateChangeCallback; 314 | }, 315 | 316 | rtcSetSignalingStateChangeCallback: function(pc, signalingStateChangeCallback) { 317 | if(!pc) return; 318 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 319 | peerConnection.rtcSignalingStateChangeCallback = signalingStateChangeCallback; 320 | }, 321 | 322 | rtcSetRemoteDescription: function(pc, pSdp, pType) { 323 | var description = new RTCSessionDescription({ 324 | sdp: UTF8ToString(pSdp), 325 | type: UTF8ToString(pType), 326 | }); 327 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 328 | peerConnection.setRemoteDescription(description) 329 | .then(function() { 330 | if(peerConnection.rtcUserDeleted) return; 331 | if(description.type == 'offer') { 332 | peerConnection.createAnswer() 333 | .then(function(answer) { 334 | return WEBRTC.handleDescription(peerConnection, answer); 335 | }) 336 | .catch(function(err) { 337 | console.error(err); 338 | }); 339 | } 340 | }) 341 | .catch(function(err) { 342 | console.error(err); 343 | }); 344 | }, 345 | 346 | rtcAddRemoteCandidate: function(pc, pCandidate, pSdpMid) { 347 | var iceCandidate = new RTCIceCandidate({ 348 | candidate: UTF8ToString(pCandidate), 349 | sdpMid: UTF8ToString(pSdpMid), 350 | }); 351 | var peerConnection = WEBRTC.peerConnectionsMap[pc]; 352 | peerConnection.addIceCandidate(iceCandidate) 353 | .catch(function(err) { 354 | console.error(err); 355 | }); 356 | }, 357 | 358 | rtcGetDataChannelLabel: function(dc, pBuffer, size) { 359 | if(!dc) return 0; 360 | var label = WEBRTC.dataChannelsMap[dc].label; 361 | stringToUTF8(label, pBuffer, size); 362 | return lengthBytesUTF8(label); 363 | }, 364 | 365 | rtcGetDataChannelUnordered: function(dc) { 366 | if(!dc) return 0; 367 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 368 | return dataChannel.ordered ? 0 : 1; 369 | }, 370 | 371 | rtcGetDataChannelMaxPacketLifeTime: function(dc) { 372 | if(!dc) return -1; 373 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 374 | return dataChannel.maxPacketLifeTime !== null ? dataChannel.maxPacketLifeTime : -1; 375 | }, 376 | 377 | rtcGetDataChannelMaxRetransmits: function(dc) { 378 | if(!dc) return -1; 379 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 380 | return dataChannel.maxRetransmits !== null ? dataChannel.maxRetransmits : -1; 381 | }, 382 | 383 | rtcSetOpenCallback: function(dc, openCallback) { 384 | if(!dc) return; 385 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 386 | var cb = function() { 387 | if(dataChannel.rtcUserDeleted) return; 388 | var userPointer = dataChannel.rtcUserPointer || 0; 389 | {{{ makeDynCall('vi', 'openCallback') }}} (userPointer); 390 | }; 391 | dataChannel.onopen = cb; 392 | if(dataChannel.readyState == 'open') setTimeout(cb, 0); 393 | }, 394 | 395 | rtcSetErrorCallback: function(dc, errorCallback) { 396 | if(!dc) return; 397 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 398 | var cb = function(evt) { 399 | if(dataChannel.rtcUserDeleted) return; 400 | var userPointer = dataChannel.rtcUserPointer || 0; 401 | var pError = evt.message ? WEBRTC.allocUTF8FromString(evt.message) : 0; 402 | {{{ makeDynCall('vii', 'errorCallback') }}} (pError, userPointer); 403 | _free(pError); 404 | }; 405 | dataChannel.onerror = cb; 406 | }, 407 | 408 | rtcSetMessageCallback: function(dc, messageCallback) { 409 | if(!dc) return; 410 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 411 | dataChannel.onmessage = function(evt) { 412 | if(dataChannel.rtcUserDeleted) return; 413 | var userPointer = dataChannel.rtcUserPointer || 0; 414 | if(typeof evt.data == 'string') { 415 | var pStr = WEBRTC.allocUTF8FromString(evt.data); 416 | {{{ makeDynCall('viii', 'messageCallback') }}} (pStr, -1, userPointer); 417 | _free(pStr); 418 | } else { 419 | var byteArray = new Uint8Array(evt.data); 420 | var size = byteArray.length; 421 | var pBuffer = _malloc(size); 422 | var heapBytes = new Uint8Array(Module['HEAPU8'].buffer, pBuffer, size); 423 | heapBytes.set(byteArray); 424 | {{{ makeDynCall('viii', 'messageCallback') }}} (pBuffer, size, userPointer); 425 | _free(pBuffer); 426 | } 427 | }; 428 | dataChannel.onclose = function() { 429 | if(dataChannel.rtcUserDeleted) return; 430 | var userPointer = dataChannel.rtcUserPointer || 0; 431 | {{{ makeDynCall('viii', 'messageCallback') }}} (0, 0, userPointer); 432 | }; 433 | }, 434 | 435 | rtcSetBufferedAmountLowCallback: function(dc, bufferedAmountLowCallback) { 436 | if(!dc) return; 437 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 438 | var cb = function(evt) { 439 | if(dataChannel.rtcUserDeleted) return; 440 | var userPointer = dataChannel.rtcUserPointer || 0; 441 | {{{ makeDynCall('vi', 'bufferedAmountLowCallback') }}} (userPointer); 442 | }; 443 | dataChannel.onbufferedamountlow = cb; 444 | }, 445 | 446 | rtcGetBufferedAmount: function(dc) { 447 | if(!dc) return 0; 448 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 449 | return dataChannel.bufferedAmount; 450 | }, 451 | 452 | rtcSetBufferedAmountLowThreshold: function(dc, threshold) { 453 | if(!dc) return; 454 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 455 | dataChannel.bufferedAmountLowThreshold = threshold; 456 | }, 457 | 458 | rtcSendMessage: function(dc, pBuffer, size) { 459 | if(!dc) return -1; 460 | var dataChannel = WEBRTC.dataChannelsMap[dc]; 461 | if(dataChannel.readyState != 'open') return -1; 462 | if(size >= 0) { 463 | var heapBytes = new Uint8Array(Module['HEAPU8'].buffer, pBuffer, size); 464 | if(heapBytes.buffer instanceof ArrayBuffer) { 465 | dataChannel.send(heapBytes); 466 | } else { 467 | var byteArray = new Uint8Array(new ArrayBuffer(size)); 468 | byteArray.set(heapBytes); 469 | dataChannel.send(byteArray); 470 | } 471 | return size; 472 | } else { 473 | var str = UTF8ToString(pBuffer); 474 | dataChannel.send(str); 475 | return lengthBytesUTF8(str); 476 | } 477 | }, 478 | 479 | rtcSetUserPointer: function(i, ptr) { 480 | if(WEBRTC.peerConnectionsMap[i]) WEBRTC.peerConnectionsMap[i].rtcUserPointer = ptr; 481 | if(WEBRTC.dataChannelsMap[i]) WEBRTC.dataChannelsMap[i].rtcUserPointer = ptr; 482 | }, 483 | }; 484 | 485 | autoAddDeps(WebRTC, '$WEBRTC'); 486 | mergeInto(LibraryManager.library, WebRTC); 487 | })(); 488 | -------------------------------------------------------------------------------- /wasm/js/websocket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | (function() { 24 | var WebSocket = { 25 | $WEBSOCKET: { 26 | map: {}, 27 | nextId: 1, 28 | 29 | allocUTF8FromString: function(str) { 30 | var strLen = lengthBytesUTF8(str); 31 | var strOnHeap = _malloc(strLen+1); 32 | stringToUTF8(str, strOnHeap, strLen+1); 33 | return strOnHeap; 34 | }, 35 | 36 | registerWebSocket: function(webSocket) { 37 | var ws = WEBSOCKET.nextId++; 38 | WEBSOCKET.map[ws] = webSocket; 39 | webSocket.binaryType = 'arraybuffer'; 40 | return ws; 41 | }, 42 | }, 43 | 44 | wsCreateWebSocket: function(pUrl) { 45 | var url = UTF8ToString(pUrl); 46 | if(!window.WebSocket) return 0; 47 | return WEBSOCKET.registerWebSocket(new WebSocket(url)); 48 | }, 49 | 50 | wsDeleteWebSocket: function(ws) { 51 | var webSocket = WEBSOCKET.map[ws]; 52 | if(webSocket) { 53 | webSocket.close(); 54 | webSocket.rtcUserDeleted = true; 55 | delete WEBSOCKET.map[ws]; 56 | } 57 | }, 58 | 59 | wsSetOpenCallback: function(ws, openCallback) { 60 | if (!ws) return; 61 | var webSocket = WEBSOCKET.map[ws]; 62 | var cb = function() { 63 | if(webSocket.rtcUserDeleted) return; 64 | var userPointer = webSocket.rtcUserPointer || 0; 65 | {{{ makeDynCall('vi', 'openCallback') }}} (userPointer); 66 | }; 67 | webSocket.onopen = cb; 68 | if(webSocket.readyState == 1) setTimeout(cb, 0); 69 | }, 70 | 71 | wsSetErrorCallback: function(ws, errorCallback) { 72 | if (!ws) return; 73 | var webSocket = WEBSOCKET.map[ws]; 74 | var cb = function() { 75 | if(webSocket.rtcUserDeleted) return; 76 | var userPointer = webSocket.rtcUserPointer || 0; 77 | {{{ makeDynCall('vii', 'errorCallback') }}} (0, userPointer); 78 | }; 79 | webSocket.onerror = cb; 80 | }, 81 | 82 | wsSetMessageCallback: function(ws, messageCallback) { 83 | if (!ws) return; 84 | var webSocket = WEBSOCKET.map[ws]; 85 | webSocket.onmessage = function(evt) { 86 | if(webSocket.rtcUserDeleted) return; 87 | if(typeof evt.data == 'string') { 88 | var pStr = WEBSOCKET.allocUTF8FromString(evt.data); 89 | var userPointer = webSocket.rtcUserPointer || 0; 90 | {{{ makeDynCall('viii', 'messageCallback') }}} (pStr, -1, userPointer); 91 | _free(pStr); 92 | } else { 93 | var byteArray = new Uint8Array(evt.data); 94 | var size = byteArray.byteLength; 95 | var pBuffer = _malloc(size); 96 | var heapBytes = new Uint8Array(Module['HEAPU8'].buffer, pBuffer, size); 97 | heapBytes.set(byteArray); 98 | var userPointer = webSocket.rtcUserPointer || 0; 99 | {{{ makeDynCall('viii', 'messageCallback') }}} (pBuffer, size, userPointer); 100 | _free(pBuffer); 101 | } 102 | }; 103 | webSocket.onclose = function() { 104 | if(webSocket.rtcUserDeleted) return; 105 | var userPointer = webSocket.rtcUserPointer || 0; 106 | {{{ makeDynCall('viii', 'messageCallback') }}} (0, 0, userPointer); 107 | }; 108 | }, 109 | 110 | wsSendMessage: function(ws, pBuffer, size) { 111 | if (!ws) return -1; 112 | var webSocket = WEBSOCKET.map[ws]; 113 | if(webSocket.readyState != 1) return -1; 114 | if(size >= 0) { 115 | var heapBytes = new Uint8Array(Module['HEAPU8'].buffer, pBuffer, size); 116 | if(heapBytes.buffer instanceof ArrayBuffer) { 117 | webSocket.send(heapBytes); 118 | } else { 119 | var byteArray = new Uint8Array(new ArrayBuffer(size)); 120 | byteArray.set(heapBytes); 121 | webSocket.send(byteArray); 122 | } 123 | return size; 124 | } else { 125 | var str = UTF8ToString(pBuffer); 126 | webSocket.send(str); 127 | return lengthBytesUTF8(str); 128 | } 129 | }, 130 | 131 | wsGetWebSocketUrl: function(ws) { 132 | if(!ws) return 0; 133 | var webSocket = WEBSOCKET.map[ws]; 134 | var url = WEBRTC.allocUTF8FromString(webSocket.url); 135 | // url should be freed later in c++. 136 | return url; 137 | }, 138 | 139 | wsGetWebSocketState: function(ws) { 140 | if(!ws) return WebSocket.CLOSED; 141 | var webSocket = WEBSOCKET.map[ws]; 142 | return webSocket.readyState; 143 | }, 144 | 145 | wsSetUserPointer: function(ws, ptr) { 146 | var webSocket = WEBSOCKET.map[ws]; 147 | if(webSocket) webSocket.rtcUserPointer = ptr; 148 | }, 149 | }; 150 | 151 | autoAddDeps(WebSocket, '$WEBSOCKET'); 152 | mergeInto(LibraryManager.library, WebSocket); 153 | })(); 154 | 155 | -------------------------------------------------------------------------------- /wasm/src/candidate.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "candidate.hpp" 24 | 25 | namespace rtc { 26 | 27 | Candidate::Candidate(const string &candidate, const string &mid) 28 | : mCandidate(candidate), mMid(mid) {} 29 | 30 | string Candidate::candidate() const { return mCandidate; } 31 | 32 | string Candidate::mid() const { return mMid; } 33 | 34 | Candidate::operator string() const { return "a=" + mCandidate; } 35 | 36 | } // namespace rtc 37 | 38 | std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) { 39 | return out << std::string(candidate); 40 | } 41 | -------------------------------------------------------------------------------- /wasm/src/channel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "channel.hpp" 24 | 25 | namespace rtc { 26 | 27 | using std::function; 28 | 29 | size_t Channel::bufferedAmount() const { return 0; /* Dummy */ } 30 | 31 | void Channel::onOpen(std::function callback) { mOpenCallback = std::move(callback); } 32 | 33 | void Channel::onClosed(std::function callback) { mClosedCallback = std::move(callback); } 34 | 35 | void Channel::onError(std::function callback) { 36 | mErrorCallback = std::move(callback); 37 | } 38 | 39 | void Channel::onMessage(std::function callback) { 40 | mMessageCallback = std::move(callback); 41 | } 42 | 43 | void Channel::onMessage(std::function binaryCallback, 44 | std::function stringCallback) { 45 | onMessage([binaryCallback = std::move(binaryCallback), 46 | stringCallback = std::move(stringCallback)](message_variant data) { 47 | std::visit(overloaded{binaryCallback, stringCallback}, std::move(data)); 48 | }); 49 | } 50 | 51 | void Channel::onBufferedAmountLow(std::function callback) { 52 | mBufferedAmountLowCallback = std::move(callback); 53 | } 54 | 55 | void Channel::setBufferedAmountLowThreshold(size_t amount) { /* Dummy */ 56 | } 57 | 58 | void Channel::triggerOpen() { 59 | if (mOpenCallback) 60 | mOpenCallback(); 61 | } 62 | 63 | void Channel::triggerClosed() { 64 | if (mClosedCallback) 65 | mClosedCallback(); 66 | } 67 | 68 | void Channel::triggerError(string error) { 69 | if (mErrorCallback) 70 | mErrorCallback(std::move(error)); 71 | } 72 | 73 | void Channel::triggerMessage(const message_variant data) { 74 | if (mMessageCallback) 75 | mMessageCallback(data); 76 | } 77 | 78 | void Channel::triggerBufferedAmountLow() { 79 | if (mBufferedAmountLowCallback) 80 | mBufferedAmountLowCallback(); 81 | } 82 | 83 | } // namespace rtc 84 | -------------------------------------------------------------------------------- /wasm/src/configuration.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "configuration.hpp" 24 | 25 | namespace rtc { 26 | 27 | IceServer::IceServer(const string &url) : hostname(url), port(0), type(Type::Dummy) {} 28 | 29 | IceServer::IceServer(string hostname_, uint16_t port_) 30 | : hostname(std::move(hostname_)), port(port_), type(Type::Stun) {} 31 | 32 | IceServer::IceServer(string hostname_, string service_) 33 | : hostname(std::move(hostname_)), type(Type::Stun) { 34 | try { 35 | port = uint16_t(std::stoul(service_)); 36 | } catch (...) { 37 | throw std::invalid_argument("Invalid ICE server port: " + service_); 38 | } 39 | } 40 | 41 | IceServer::IceServer(string hostname_, uint16_t port_, string username_, string password_, 42 | RelayType relayType_) 43 | : hostname(std::move(hostname_)), port(port_), type(Type::Turn), username(std::move(username_)), 44 | password(std::move(password_)), relayType(relayType_) {} 45 | 46 | IceServer::IceServer(string hostname_, string service_, string username_, string password_, 47 | RelayType relayType_) 48 | : hostname(std::move(hostname_)), type(Type::Turn), username(std::move(username_)), 49 | password(std::move(password_)), relayType(relayType_) { 50 | try { 51 | port = uint16_t(std::stoul(service_)); 52 | } catch (...) { 53 | throw std::invalid_argument("Invalid ICE server port: " + service_); 54 | } 55 | } 56 | 57 | } // namespace rtc 58 | -------------------------------------------------------------------------------- /wasm/src/datachannel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "datachannel.hpp" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | extern "C" { 32 | extern void rtcDeleteDataChannel(int dc); 33 | extern int rtcGetDataChannelLabel(int dc, char *buffer, int size); 34 | extern int rtcGetDataChannelUnordered(int dc); 35 | extern int rtcGetDataChannelMaxPacketLifeTime(int dc); 36 | extern int rtcGetDataChannelMaxRetransmits(int dc); 37 | extern void rtcSetOpenCallback(int dc, void (*openCallback)(void *)); 38 | extern void rtcSetErrorCallback(int dc, void (*errorCallback)(const char *, void *)); 39 | extern void rtcSetMessageCallback(int dc, void (*messageCallback)(const char *, int, void *)); 40 | extern void rtcSetBufferedAmountLowCallback(int dc, void (*bufferedAmountLowCallback)(void *)); 41 | extern int rtcGetBufferedAmount(int dc); 42 | extern void rtcSetBufferedAmountLowThreshold(int dc, int threshold); 43 | extern int rtcSendMessage(int dc, const char *buffer, int size); 44 | extern void rtcSetUserPointer(int i, void *ptr); 45 | } 46 | 47 | namespace rtc { 48 | 49 | using std::function; 50 | 51 | void DataChannel::OpenCallback(void *ptr) { 52 | DataChannel *d = static_cast(ptr); 53 | if (d) 54 | d->triggerOpen(); 55 | } 56 | 57 | void DataChannel::ErrorCallback(const char *error, void *ptr) { 58 | DataChannel *d = static_cast(ptr); 59 | if (d) 60 | d->triggerError(string(error ? error : "unknown")); 61 | } 62 | 63 | void DataChannel::MessageCallback(const char *data, int size, void *ptr) { 64 | DataChannel *d = static_cast(ptr); 65 | if (d) { 66 | if (data) { 67 | if (size >= 0) { 68 | auto *b = reinterpret_cast(data); 69 | d->triggerMessage(binary(b, b + size)); 70 | } else { 71 | d->triggerMessage(string(data)); 72 | } 73 | } else { 74 | d->close(); 75 | d->triggerClosed(); 76 | } 77 | } 78 | } 79 | 80 | void DataChannel::BufferedAmountLowCallback(void *ptr) { 81 | DataChannel *d = static_cast(ptr); 82 | if (d) { 83 | d->triggerBufferedAmountLow(); 84 | } 85 | } 86 | 87 | DataChannel::DataChannel(int id) : mId(id), mConnected(false) { 88 | rtcSetUserPointer(mId, this); 89 | rtcSetOpenCallback(mId, OpenCallback); 90 | rtcSetErrorCallback(mId, ErrorCallback); 91 | rtcSetMessageCallback(mId, MessageCallback); 92 | rtcSetBufferedAmountLowCallback(mId, BufferedAmountLowCallback); 93 | 94 | char str[256]; 95 | rtcGetDataChannelLabel(mId, str, 256); 96 | mLabel = str; 97 | } 98 | 99 | DataChannel::~DataChannel() { close(); } 100 | 101 | void DataChannel::close() { 102 | mConnected = false; 103 | if (mId) { 104 | rtcDeleteDataChannel(mId); 105 | mId = 0; 106 | } 107 | } 108 | 109 | bool DataChannel::send(message_variant message) { 110 | if (!mId) 111 | return false; 112 | 113 | return std::visit( 114 | overloaded{[this](const binary &b) { 115 | auto data = reinterpret_cast(b.data()); 116 | return rtcSendMessage(mId, data, int(b.size())) >= 0; 117 | }, 118 | [this](const string &s) { return rtcSendMessage(mId, s.c_str(), -1) >= 0; }}, 119 | std::move(message)); 120 | } 121 | 122 | bool DataChannel::send(const byte *data, size_t size) { 123 | if (!mId) 124 | return false; 125 | 126 | return rtcSendMessage(mId, reinterpret_cast(data), int(size)) >= 0; 127 | } 128 | 129 | bool DataChannel::isOpen() const { return mConnected; } 130 | 131 | bool DataChannel::isClosed() const { return mId == 0; } 132 | 133 | size_t DataChannel::bufferedAmount() const { 134 | if (!mId) 135 | return 0; 136 | 137 | int ret = rtcGetBufferedAmount(mId); 138 | if (ret < 0) 139 | return 0; 140 | 141 | return size_t(ret); 142 | } 143 | 144 | std::string DataChannel::label() const { return mLabel; } 145 | 146 | Reliability DataChannel::reliability() const { 147 | Reliability reliability = {}; 148 | 149 | if (!mId) 150 | return reliability; 151 | 152 | reliability.unordered = rtcGetDataChannelUnordered(mId) ? true : false; 153 | 154 | int maxRetransmits = rtcGetDataChannelMaxRetransmits(mId); 155 | int maxPacketLifeTime = rtcGetDataChannelMaxPacketLifeTime(mId); 156 | 157 | if (maxRetransmits >= 0) 158 | reliability.maxRetransmits = unsigned(maxRetransmits); 159 | 160 | if (maxPacketLifeTime >= 0) 161 | reliability.maxPacketLifeTime = std::chrono::milliseconds(maxPacketLifeTime); 162 | 163 | return reliability; 164 | } 165 | 166 | void DataChannel::setBufferedAmountLowThreshold(size_t amount) { 167 | if (!mId) 168 | return; 169 | 170 | rtcSetBufferedAmountLowThreshold(mId, int(amount)); 171 | } 172 | 173 | void DataChannel::triggerOpen() { 174 | mConnected = true; 175 | Channel::triggerOpen(); 176 | } 177 | 178 | } // namespace rtc 179 | -------------------------------------------------------------------------------- /wasm/src/description.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "description.hpp" 24 | 25 | #include 26 | 27 | namespace rtc { 28 | 29 | Description::Description(const string &sdp, Type type) : mSdp(sdp), mType(typeToString(type)) {} 30 | 31 | Description::Description(const string &sdp, string typeString) 32 | : mSdp(sdp), mType(std::move(typeString)) {} 33 | 34 | Description::Type Description::type() const { return stringToType(mType); } 35 | 36 | string Description::typeString() const { return mType; } 37 | 38 | Description::operator string() const { return mSdp; } 39 | 40 | Description::Type Description::stringToType(const string &typeString) { 41 | using TypeMap_t = std::unordered_map; 42 | static const TypeMap_t TypeMap = {{"unspec", Type::Unspec}, 43 | {"offer", Type::Offer}, 44 | {"answer", Type::Answer}, 45 | {"pranswer", Type::Pranswer}, 46 | {"rollback", Type::Rollback}}; 47 | auto it = TypeMap.find(typeString); 48 | return it != TypeMap.end() ? it->second : Type::Unspec; 49 | } 50 | 51 | string Description::typeToString(Type type) { 52 | switch (type) { 53 | case Type::Unspec: 54 | return "unspec"; 55 | case Type::Offer: 56 | return "offer"; 57 | case Type::Answer: 58 | return "answer"; 59 | case Type::Pranswer: 60 | return "pranswer"; 61 | case Type::Rollback: 62 | return "rollback"; 63 | default: 64 | return "unknown"; 65 | } 66 | } 67 | 68 | } // namespace rtc 69 | 70 | std::ostream &operator<<(std::ostream &out, const rtc::Description &description) { 71 | return out << std::string(description); 72 | } 73 | 74 | std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) { 75 | return out << rtc::Description::typeToString(type); 76 | } 77 | -------------------------------------------------------------------------------- /wasm/src/global.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2024 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "global.hpp" 24 | 25 | namespace rtc { 26 | 27 | void InitLogger([[maybe_unused]] LogLevel level, [[maybe_unused]] LogCallback callback) { 28 | // Dummy 29 | } 30 | 31 | void Preload() { 32 | // Dummy 33 | } 34 | 35 | std::shared_future Cleanup() { 36 | // Dummy 37 | std::promise p; 38 | p.set_value(); 39 | return p.get_future(); 40 | } 41 | 42 | std::ostream &operator<<(std::ostream &out, LogLevel level) { 43 | switch (level) { 44 | case LogLevel::Fatal: 45 | out << "fatal"; 46 | break; 47 | case LogLevel::Error: 48 | out << "error"; 49 | break; 50 | case LogLevel::Warning: 51 | out << "warning"; 52 | break; 53 | case LogLevel::Info: 54 | out << "info"; 55 | break; 56 | case LogLevel::Debug: 57 | out << "debug"; 58 | break; 59 | case LogLevel::Verbose: 60 | out << "verbose"; 61 | break; 62 | default: 63 | out << "none"; 64 | break; 65 | } 66 | return out; 67 | } 68 | 69 | } // namespace rtc 70 | -------------------------------------------------------------------------------- /wasm/src/peerconnection.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "peerconnection.hpp" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | extern "C" { 32 | extern int rtcCreatePeerConnection(const char **pUrls, const char **pUsernames, 33 | const char **pPasswords, int nIceServers); 34 | extern void rtcDeletePeerConnection(int pc); 35 | extern char *rtcGetLocalDescription(int pc); 36 | extern char *rtcGetLocalDescriptionType(int pc); 37 | extern char *rtcGetRemoteDescription(int pc); 38 | extern char *rtcGetRemoteDescriptionType(int pc); 39 | extern int rtcCreateDataChannel(int pc, const char *label, bool unordered, int maxRetransmits, 40 | int maxPacketLifeTime); 41 | extern void rtcSetDataChannelCallback(int pc, void (*dataChannelCallback)(int, void *)); 42 | extern void rtcSetLocalDescriptionCallback(int pc, 43 | void (*descriptionCallback)(const char *, const char *, 44 | void *)); 45 | extern void rtcSetLocalCandidateCallback(int pc, void (*candidateCallback)(const char *, 46 | const char *, void *)); 47 | extern void rtcSetStateChangeCallback(int pc, void (*stateChangeCallback)(int, void *)); 48 | extern void rtcSetIceStateChangeCallback(int pc, void (*iceStateChangeCallback)(int, void *)); 49 | extern void rtcSetGatheringStateChangeCallback(int pc, 50 | void (*gatheringStateChangeCallback)(int, void *)); 51 | extern void rtcSetSignalingStateChangeCallback(int pc, 52 | void (*signalingStateChangeCallback)(int, void *)); 53 | extern void rtcSetRemoteDescription(int pc, const char *sdp, const char *type); 54 | extern void rtcAddRemoteCandidate(int pc, const char *candidate, const char *mid); 55 | extern void rtcSetUserPointer(int i, void *ptr); 56 | } 57 | 58 | namespace rtc { 59 | 60 | using std::function; 61 | using std::vector; 62 | 63 | void PeerConnection::DataChannelCallback(int dc, void *ptr) { 64 | PeerConnection *p = static_cast(ptr); 65 | if (p) 66 | p->triggerDataChannel(std::make_shared(dc)); 67 | } 68 | 69 | void PeerConnection::DescriptionCallback(const char *sdp, const char *type, void *ptr) { 70 | PeerConnection *p = static_cast(ptr); 71 | if (p) 72 | p->triggerLocalDescription(Description(sdp, type)); 73 | } 74 | 75 | void PeerConnection::CandidateCallback(const char *candidate, const char *mid, void *ptr) { 76 | PeerConnection *p = static_cast(ptr); 77 | if (p) 78 | p->triggerLocalCandidate(Candidate(candidate, mid)); 79 | } 80 | 81 | void PeerConnection::StateChangeCallback(int state, void *ptr) { 82 | PeerConnection *p = static_cast(ptr); 83 | if (p) 84 | p->triggerStateChange(static_cast(state)); 85 | } 86 | 87 | void PeerConnection::IceStateChangeCallback(int state, void *ptr) { 88 | PeerConnection *p = static_cast(ptr); 89 | if (p) 90 | p->triggerIceStateChange(static_cast(state)); 91 | } 92 | 93 | void PeerConnection::GatheringStateChangeCallback(int state, void *ptr) { 94 | PeerConnection *p = static_cast(ptr); 95 | if (p) 96 | p->triggerGatheringStateChange(static_cast(state)); 97 | } 98 | 99 | void PeerConnection::SignalingStateChangeCallback(int state, void *ptr) { 100 | PeerConnection *p = static_cast(ptr); 101 | if (p) 102 | p->triggerSignalingStateChange(static_cast(state)); 103 | } 104 | 105 | PeerConnection::PeerConnection(const Configuration &config) { 106 | vector urls; 107 | urls.reserve(config.iceServers.size()); 108 | for (const IceServer &iceServer : config.iceServers) { 109 | string url; 110 | if (iceServer.type == IceServer::Type::Dummy) { 111 | url = iceServer.hostname; 112 | } else { 113 | string scheme = 114 | iceServer.type == IceServer::Type::Turn 115 | ? (iceServer.relayType == IceServer::RelayType::TurnTls ? "turns" : "turn") 116 | : "stun"; 117 | 118 | url += scheme + ":" + iceServer.hostname; 119 | 120 | if (iceServer.port != 0) 121 | url += string(":") + std::to_string(iceServer.port); 122 | 123 | if (iceServer.type == IceServer::Type::Turn && 124 | iceServer.relayType != IceServer::RelayType::TurnUdp) 125 | url += "?transport=tcp"; 126 | } 127 | urls.push_back(url); 128 | } 129 | 130 | vector url_ptrs; 131 | vector username_ptrs; 132 | vector password_ptrs; 133 | url_ptrs.reserve(config.iceServers.size()); 134 | username_ptrs.reserve(config.iceServers.size()); 135 | password_ptrs.reserve(config.iceServers.size()); 136 | for (const string &s : urls) 137 | url_ptrs.push_back(s.c_str()); 138 | for (const IceServer &iceServer : config.iceServers) { 139 | username_ptrs.push_back(iceServer.username.c_str()); 140 | password_ptrs.push_back(iceServer.password.c_str()); 141 | } 142 | mId = rtcCreatePeerConnection(url_ptrs.data(), username_ptrs.data(), password_ptrs.data(), 143 | config.iceServers.size()); 144 | if (!mId) 145 | throw std::runtime_error("WebRTC not supported"); 146 | 147 | rtcSetUserPointer(mId, this); 148 | rtcSetDataChannelCallback(mId, DataChannelCallback); 149 | rtcSetLocalDescriptionCallback(mId, DescriptionCallback); 150 | rtcSetLocalCandidateCallback(mId, CandidateCallback); 151 | rtcSetStateChangeCallback(mId, StateChangeCallback); 152 | rtcSetIceStateChangeCallback(mId, IceStateChangeCallback); 153 | rtcSetGatheringStateChangeCallback(mId, GatheringStateChangeCallback); 154 | rtcSetSignalingStateChangeCallback(mId, SignalingStateChangeCallback); 155 | } 156 | 157 | PeerConnection::~PeerConnection() { close(); } 158 | 159 | void PeerConnection::close() { 160 | if (mId) { 161 | rtcDeletePeerConnection(mId); 162 | mId = 0; 163 | } 164 | } 165 | 166 | PeerConnection::State PeerConnection::state() const { return mState; } 167 | 168 | PeerConnection::IceState PeerConnection::iceState() const { return mIceState; } 169 | 170 | PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; } 171 | 172 | PeerConnection::SignalingState PeerConnection::signalingState() const { return mSignalingState; } 173 | 174 | optional PeerConnection::localDescription() const { 175 | if (!mId) 176 | return std::nullopt; 177 | 178 | char *sdp = rtcGetLocalDescription(mId); 179 | char *type = rtcGetLocalDescriptionType(mId); 180 | if (!sdp || !type) { 181 | free(sdp); 182 | free(type); 183 | return std::nullopt; 184 | } 185 | Description description(sdp, type); 186 | free(sdp); 187 | free(type); 188 | return description; 189 | } 190 | 191 | optional PeerConnection::remoteDescription() const { 192 | if (!mId) 193 | return std::nullopt; 194 | 195 | char *sdp = rtcGetRemoteDescription(mId); 196 | char *type = rtcGetRemoteDescriptionType(mId); 197 | if (!sdp || !type) { 198 | free(sdp); 199 | free(type); 200 | return std::nullopt; 201 | } 202 | Description description(sdp, type); 203 | free(sdp); 204 | free(type); 205 | return description; 206 | } 207 | 208 | shared_ptr PeerConnection::createDataChannel(const string &label, 209 | DataChannelInit init) { 210 | if (!mId) 211 | throw std::runtime_error("Peer connection is closed"); 212 | 213 | const Reliability &reliability = init.reliability; 214 | if (reliability.maxPacketLifeTime && reliability.maxRetransmits) 215 | throw std::invalid_argument("Both maxPacketLifeTime and maxRetransmits are set"); 216 | 217 | int maxRetransmits = reliability.maxRetransmits ? int(*reliability.maxRetransmits) : -1; 218 | int maxPacketLifeTime = 219 | reliability.maxPacketLifeTime ? int(reliability.maxPacketLifeTime->count()) : -1; 220 | 221 | return std::make_shared(rtcCreateDataChannel( 222 | mId, label.c_str(), init.reliability.unordered, maxRetransmits, maxPacketLifeTime)); 223 | } 224 | 225 | void PeerConnection::setRemoteDescription(const Description &description) { 226 | if (!mId) 227 | throw std::runtime_error("Peer connection is closed"); 228 | 229 | rtcSetRemoteDescription(mId, string(description).c_str(), description.typeString().c_str()); 230 | } 231 | 232 | void PeerConnection::addRemoteCandidate(const Candidate &candidate) { 233 | if (!mId) 234 | throw std::runtime_error("Peer connection is closed"); 235 | 236 | rtcAddRemoteCandidate(mId, candidate.candidate().c_str(), candidate.mid().c_str()); 237 | } 238 | 239 | void PeerConnection::onDataChannel(function)> callback) { 240 | mDataChannelCallback = callback; 241 | } 242 | 243 | void PeerConnection::onLocalDescription(function callback) { 244 | mLocalDescriptionCallback = callback; 245 | } 246 | 247 | void PeerConnection::onLocalCandidate(function callback) { 248 | mLocalCandidateCallback = callback; 249 | } 250 | 251 | void PeerConnection::onStateChange(function callback) { 252 | mStateChangeCallback = callback; 253 | } 254 | 255 | void PeerConnection::onIceStateChange(function callback) { 256 | mIceStateChangeCallback = callback; 257 | } 258 | 259 | void PeerConnection::onGatheringStateChange(function callback) { 260 | mGatheringStateChangeCallback = callback; 261 | } 262 | 263 | void PeerConnection::onSignalingStateChange(function callback) { 264 | mSignalingStateChangeCallback = callback; 265 | } 266 | 267 | void PeerConnection::triggerDataChannel(shared_ptr dataChannel) { 268 | if (mDataChannelCallback) 269 | mDataChannelCallback(dataChannel); 270 | } 271 | 272 | void PeerConnection::triggerLocalDescription(const Description &description) { 273 | if (mLocalDescriptionCallback) 274 | mLocalDescriptionCallback(description); 275 | } 276 | 277 | void PeerConnection::triggerLocalCandidate(const Candidate &candidate) { 278 | if (mLocalCandidateCallback) 279 | mLocalCandidateCallback(candidate); 280 | } 281 | 282 | void PeerConnection::triggerStateChange(State state) { 283 | mState = state; 284 | if (mStateChangeCallback) 285 | mStateChangeCallback(state); 286 | } 287 | 288 | void PeerConnection::triggerIceStateChange(IceState state) { 289 | mIceState = state; 290 | if (mIceStateChangeCallback) 291 | mIceStateChangeCallback(state); 292 | } 293 | 294 | void PeerConnection::triggerGatheringStateChange(GatheringState state) { 295 | mGatheringState = state; 296 | if (mGatheringStateChangeCallback) 297 | mGatheringStateChangeCallback(state); 298 | } 299 | 300 | void PeerConnection::triggerSignalingStateChange(SignalingState state) { 301 | mSignalingState = state; 302 | if (mSignalingStateChangeCallback) 303 | mSignalingStateChangeCallback(state); 304 | } 305 | 306 | std::ostream &operator<<(std::ostream &out, PeerConnection::State state) { 307 | using State = PeerConnection::State; 308 | const char *str; 309 | switch (state) { 310 | case State::New: 311 | str = "new"; 312 | break; 313 | case State::Connecting: 314 | str = "connecting"; 315 | break; 316 | case State::Connected: 317 | str = "connected"; 318 | break; 319 | case State::Disconnected: 320 | str = "disconnected"; 321 | break; 322 | case State::Failed: 323 | str = "failed"; 324 | break; 325 | case State::Closed: 326 | str = "closed"; 327 | break; 328 | default: 329 | str = "unknown"; 330 | break; 331 | } 332 | return out << str; 333 | } 334 | 335 | std::ostream &operator<<(std::ostream &out, PeerConnection::IceState state) { 336 | using IceState = PeerConnection::IceState; 337 | const char *str; 338 | switch (state) { 339 | case IceState::New: 340 | str = "new"; 341 | break; 342 | case IceState::Checking: 343 | str = "checking"; 344 | break; 345 | case IceState::Connected: 346 | str = "connected"; 347 | break; 348 | case IceState::Completed: 349 | str = "completed"; 350 | break; 351 | case IceState::Failed: 352 | str = "failed"; 353 | break; 354 | case IceState::Disconnected: 355 | str = "disconnected"; 356 | break; 357 | case IceState::Closed: 358 | str = "closed"; 359 | break; 360 | default: 361 | str = "unknown"; 362 | break; 363 | } 364 | return out << str; 365 | } 366 | 367 | std::ostream &operator<<(std::ostream &out, PeerConnection::GatheringState state) { 368 | using GatheringState = PeerConnection::GatheringState; 369 | const char *str; 370 | switch (state) { 371 | case GatheringState::New: 372 | str = "new"; 373 | break; 374 | case GatheringState::InProgress: 375 | str = "in-progress"; 376 | break; 377 | case GatheringState::Complete: 378 | str = "complete"; 379 | break; 380 | default: 381 | str = "unknown"; 382 | break; 383 | } 384 | return out << str; 385 | } 386 | 387 | std::ostream &operator<<(std::ostream &out, PeerConnection::SignalingState state) { 388 | using SignalingState = PeerConnection::SignalingState; 389 | const char *str; 390 | switch (state) { 391 | case SignalingState::Stable: 392 | str = "stable"; 393 | break; 394 | case SignalingState::HaveLocalOffer: 395 | str = "have-local-offer"; 396 | break; 397 | case SignalingState::HaveRemoteOffer: 398 | str = "have-remote-offer"; 399 | break; 400 | case SignalingState::HaveLocalPranswer: 401 | str = "have-local-pranswer"; 402 | break; 403 | case SignalingState::HaveRemotePranswer: 404 | str = "have-remote-pranswer"; 405 | break; 406 | default: 407 | str = "unknown"; 408 | break; 409 | } 410 | return out << str; 411 | } 412 | 413 | } // namespace rtc 414 | -------------------------------------------------------------------------------- /wasm/src/websocket.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-2022 Paul-Louis Ageneau 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "websocket.hpp" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | extern "C" { 31 | extern int wsCreateWebSocket(const char *url); 32 | extern void wsDeleteWebSocket(int ws); 33 | extern void wsSetOpenCallback(int ws, void (*openCallback)(void *)); 34 | extern void wsSetErrorCallback(int ws, void (*errorCallback)(const char *, void *)); 35 | extern void wsSetMessageCallback(int ws, void (*messageCallback)(const char *, int, void *)); 36 | extern int wsSendMessage(int ws, const char *buffer, int size); 37 | extern char *wsGetWebSocketUrl(int ws); 38 | extern int wsGetWebSocketState(int ws); 39 | extern void wsSetUserPointer(int ws, void *ptr); 40 | } 41 | 42 | namespace rtc { 43 | 44 | void WebSocket::OpenCallback(void *ptr) { 45 | WebSocket *w = static_cast(ptr); 46 | if (w) 47 | w->triggerOpen(); 48 | } 49 | 50 | void WebSocket::ErrorCallback(const char *error, void *ptr) { 51 | WebSocket *w = static_cast(ptr); 52 | if (w) 53 | w->triggerError(string(error ? error : "unknown")); 54 | } 55 | 56 | void WebSocket::MessageCallback(const char *data, int size, void *ptr) { 57 | WebSocket *w = static_cast(ptr); 58 | if (w) { 59 | if (data) { 60 | if (size >= 0) { 61 | auto b = reinterpret_cast(data); 62 | w->triggerMessage(binary(b, b + size)); 63 | } else { 64 | w->triggerMessage(string(data)); 65 | } 66 | } else { 67 | w->close(); 68 | w->triggerClosed(); 69 | } 70 | } 71 | } 72 | 73 | WebSocket::WebSocket() : mId(0), mConnected(false) {} 74 | 75 | WebSocket::~WebSocket() { close(); } 76 | 77 | void WebSocket::open(const string &url) { 78 | close(); 79 | 80 | mId = wsCreateWebSocket(url.c_str()); 81 | if (!mId) 82 | throw std::runtime_error("WebSocket not supported"); 83 | 84 | wsSetUserPointer(mId, this); 85 | wsSetOpenCallback(mId, OpenCallback); 86 | wsSetErrorCallback(mId, ErrorCallback); 87 | wsSetMessageCallback(mId, MessageCallback); 88 | } 89 | 90 | void WebSocket::close() { 91 | mConnected = false; 92 | if (mId) { 93 | wsDeleteWebSocket(mId); 94 | mId = 0; 95 | } 96 | } 97 | 98 | bool WebSocket::isOpen() const { return mConnected; } 99 | 100 | bool WebSocket::isClosed() const { return mId == 0; } 101 | 102 | bool WebSocket::send(message_variant message) { 103 | if (!mId) 104 | return false; 105 | 106 | return std::visit( 107 | overloaded{[this](const binary &b) { 108 | auto data = reinterpret_cast(b.data()); 109 | return wsSendMessage(mId, data, int(b.size())) >= 0; 110 | }, 111 | [this](const string &s) { return wsSendMessage(mId, s.c_str(), -1) >= 0; }}, 112 | std::move(message)); 113 | } 114 | 115 | bool WebSocket::send(const byte *data, size_t size) { 116 | if (!mId) 117 | return false; 118 | 119 | return wsSendMessage(mId, reinterpret_cast(data), int(size)) >= 0; 120 | } 121 | 122 | WebSocket::State WebSocket::readyState() const { 123 | if (!mId) 124 | return State::Closed; 125 | 126 | return static_cast(wsGetWebSocketState(mId)); 127 | } 128 | 129 | optional WebSocket::url() const { 130 | if (mId) { 131 | char *url = wsGetWebSocketUrl(mId); 132 | if (url) { 133 | string result(url); 134 | free(url); 135 | return result; 136 | } 137 | } 138 | return std::nullopt; 139 | } 140 | 141 | void WebSocket::triggerOpen() { 142 | mConnected = true; 143 | Channel::triggerOpen(); 144 | } 145 | 146 | std::ostream &operator<<(std::ostream &out, WebSocket::State state) { 147 | using State = WebSocket::State; 148 | const char *str; 149 | switch (state) { 150 | case State::Connecting: 151 | str = "connecting"; 152 | break; 153 | case State::Open: 154 | str = "open"; 155 | break; 156 | case State::Closing: 157 | str = "closing"; 158 | break; 159 | case State::Closed: 160 | str = "closed"; 161 | break; 162 | default: 163 | str = "unknown"; 164 | break; 165 | } 166 | return out << str; 167 | } 168 | 169 | } // namespace rtc 170 | --------------------------------------------------------------------------------