Browse Source

Reordered C API functions for clarity

Paul-Louis Ageneau 4 years ago
parent
commit
38b30f65d9
3 changed files with 918 additions and 924 deletions
  1. 120 116
      DOC.md
  2. 32 41
      include/rtc/rtc.h
  3. 766 767
      src/capi.cpp

+ 120 - 116
DOC.md

@@ -327,6 +327,122 @@ Return value: the maximun length of strings copied in buffers (including the ter
 
 
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 
 
+### Channel (Common API for Data Channel, Track, and WebSocket)
+
+The following common functions might be called with a generic channel identifier. It may be the identifier of either a Data Channel, a Track, or a WebSocket.
+
+#### rtcSetXCallback
+
+These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a channel.
+
+```
+int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myOpenCallback(int id, void *user_ptr)`
+
+```
+int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myClosedCallback(int id, void *user_ptr)`
+
+```
+int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myErrorCallback(int id, const char *error, void *user_ptr)`
+
+```
+int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myMessageCallback(int id, const char *message, int size, void *user_ptr)`
+
+```
+int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myBufferedAmountLowCallback(int id, void *user_ptr)`
+
+```
+int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myAvailableCallback(int id, void *user_ptr)`
+
+#### rtcSendMessage
+
+```
+int rtcSendMessage(int id, const char *data, int size)
+```
+
+Arguments:
+- `id`: the channel identifier
+- `data`: the message data
+- `size`: if size >= 0, `data` is interpreted as a binary message of length `size`, otherwise it is interpreted as a null-terminated UTF-8 string.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+Sends a message immediately if possible.
+
+Data Channel and WebSocket: If the message may not be sent immediately due to flow control or congestion control, it is buffered until it can actually be sent. You can retrieve the current buffered data size with `rtcGetBufferedAmount`.
+Tracks are an exception: There is no flow or congestion control, messages are never buffered and `rtcGetBufferedAmount` always returns 0.
+
+#### rtcGetBufferedAmount
+
+```
+int rtcGetBufferedAmount(int id)
+```
+
+Retrieves the current buffered amount, i.e. the total size of currently buffered messages waiting to be actually sent in the channel. This does not account for the data buffered at the transport level.
+
+Return value: the buffered amount or a negative error code
+
+#### rtcGetBufferedAmountLowThreshold
+
+```
+int rtcSetBufferedAmountLowThreshold(int id, int amount)
+```
+
+Changes the buffered amount threshold under which `BufferedAmountLowCallback` is called. The callback is called when the buffered amount was strictly superior and gets equal to or lower than the threshold when a message is sent. The initial threshold is 0, meaning the the callback is called each time the buffered amount goes back to zero after being non-zero.
+
+Arguments:
+- `id`: the channel identifier
+- `amount`: the new buffer level threshold
+
+Return value: the identifier of the new WebSocket or a negative error code
+
+#### rtcReceiveMessage
+
+```
+int rtcReceiveMessage(int id, char *buffer, int *size)
+```
+
+Receives a pending message if possible. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+- `id`: the channel identifier
+- `buffer`: a user-supplied buffer where to write the message data
+- `size`: a pointer to a user-supplied int which must be initialized to the size of `buffer`. On success, the function will write the size of the message to it before returning.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code (In particular, `RTC_ERR_NOT_AVAIL` is returned when there are no pending messages)
+
+If `buffer` is `NULL`, the message is not copied and kept pending but the size is still written to `size`.
+
+#### rtcGetAvailableAmount
+
+```
+int rtcGetAvailableAmount(int id)
+```
+
+Retrieves the available amount, i.e. the total size of messages pending reception with `rtcReceiveMessage`. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+- `id`: the channel identifier
+
+Return value: the available amount or a negative error code
+
 ### Data Channel
 ### Data Channel
 
 
 #### rtcCreateDataChannel
 #### rtcCreateDataChannel
@@ -499,6 +615,10 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
+### Media
+
+TODO
+
 ### WebSocket
 ### WebSocket
 
 
 #### rtcCreateWebSocket
 #### rtcCreateWebSocket
@@ -625,120 +745,4 @@ Arguments:
 
 
 Return value: The port of the WebSocket Server or a negative error code
 Return value: The port of the WebSocket Server or a negative error code
 
 
-### Channel (Common API for Data Channel, Track, and WebSocket)
-
-The following common functions might be called with a generic channel identifier. It may be the identifier of either a Data Channel, a Track, or a WebSocket.
-
-#### rtcSetXCallback
-
-These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a channel.
-
-```
-int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myOpenCallback(int id, void *user_ptr)`
-
-```
-int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myClosedCallback(int id, void *user_ptr)`
-
-```
-int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myErrorCallback(int id, const char *error, void *user_ptr)`
-
-```
-int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myMessageCallback(int id, const char *message, int size, void *user_ptr)`
-
-```
-int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myBufferedAmountLowCallback(int id, void *user_ptr)`
-
-```
-int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myAvailableCallback(int id, void *user_ptr)`
-
-#### rtcSendMessage
-
-```
-int rtcSendMessage(int id, const char *data, int size)
-```
-
-Arguments:
-- `id`: the channel identifier
-- `data`: the message data
-- `size`: if size >= 0, `data` is interpreted as a binary message of length `size`, otherwise it is interpreted as a null-terminated UTF-8 string.
-
-Return value: `RTC_ERR_SUCCESS` or a negative error code
-
-Sends a message immediately if possible.
-
-Data Channel and WebSocket: If the message may not be sent immediately due to flow control or congestion control, it is buffered until it can actually be sent. You can retrieve the current buffered data size with `rtcGetBufferedAmount`.
-Tracks are an exception: There is no flow or congestion control, messages are never buffered and `rtcGetBufferedAmount` always returns 0.
-
-#### rtcGetBufferedAmount
-
-```
-int rtcGetBufferedAmount(int id)
-```
-
-Retrieves the current buffered amount, i.e. the total size of currently buffered messages waiting to be actually sent in the channel. This does not account for the data buffered at the transport level.
-
-Return value: the buffered amount or a negative error code
-
-#### rtcGetBufferedAmountLowThreshold
-
-```
-int rtcSetBufferedAmountLowThreshold(int id, int amount)
-```
-
-Changes the buffered amount threshold under which `BufferedAmountLowCallback` is called. The callback is called when the buffered amount was strictly superior and gets equal to or lower than the threshold when a message is sent. The initial threshold is 0, meaning the the callback is called each time the buffered amount goes back to zero after being non-zero.
-
-Arguments:
-- `id`: the channel identifier
-- `amount`: the new buffer level threshold
-
-Return value: the identifier of the new WebSocket or a negative error code
-
-#### rtcReceiveMessage
-
-```
-int rtcReceiveMessage(int id, char *buffer, int *size)
-```
-
-Receives a pending message if possible. The function may only be called if `MessageCallback` is not set.
-
-Arguments:
-- `id`: the channel identifier
-- `buffer`: a user-supplied buffer where to write the message data
-- `size`: a pointer to a user-supplied int which must be initialized to the size of `buffer`. On success, the function will write the size of the message to it before returning.
-
-Return value: `RTC_ERR_SUCCESS` or a negative error code (In particular, `RTC_ERR_NOT_AVAIL` is returned when there are no pending messages)
-
-If `buffer` is `NULL`, the message is not copied and kept pending but the size is still written to `size`.
-
-#### rtcGetAvailableAmount
-
-```
-int rtcGetAvailableAmount(int id)
-```
-
-Retrieves the available amount, i.e. the total size of messages pending reception with `rtcReceiveMessage`. The function may only be called if `MessageCallback` is not set.
-
-Arguments:
-- `id`: the channel identifier
-
-Return value: the available amount or a negative error code
-
 
 

+ 32 - 41
include/rtc/rtc.h

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2019 Paul-Louis Ageneau
+ * Copyright (c) 2019-2021 Paul-Louis Ageneau
  *
  *
  * This library is free software; you can redistribute it and/or
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * modify it under the terms of the GNU Lesser General Public
@@ -113,10 +113,7 @@ typedef enum {
 	RTC_DIRECTION_INACTIVE = 4
 	RTC_DIRECTION_INACTIVE = 4
 } rtcDirection;
 } rtcDirection;
 
 
-typedef enum {
-	RTC_TRANSPORT_POLICY_ALL = 0,
-	RTC_TRANSPORT_POLICY_RELAY = 1
-} rtcTransportPolicy;
+typedef enum { RTC_TRANSPORT_POLICY_ALL = 0, RTC_TRANSPORT_POLICY_RELAY = 1 } rtcTransportPolicy;
 
 
 #define RTC_ERR_SUCCESS 0
 #define RTC_ERR_SUCCESS 0
 #define RTC_ERR_INVALID -1   // invalid argument
 #define RTC_ERR_INVALID -1   // invalid argument
@@ -191,6 +188,25 @@ RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
 RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
                                            int remoteSize);
                                            int remoteSize);
 
 
+// DataChannel, Track, and WebSocket common API
+
+RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
+RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
+RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
+RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
+RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
+RTC_EXPORT int rtcIsOpen(int id);
+
+RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
+RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
+RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
+
+// DataChannel, Track, and WebSocket common extended API
+
+RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
+RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
+RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
+
 // DataChannel
 // DataChannel
 
 
 typedef struct {
 typedef struct {
@@ -212,7 +228,6 @@ RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcCreateDataChannelEx(int pc, const char *label,
 RTC_EXPORT int rtcCreateDataChannelEx(int pc, const char *label,
                                       const rtcDataChannelInit *init); // returns dc id
                                       const rtcDataChannelInit *init); // returns dc id
-RTC_EXPORT int rtcIsOpen(int dc);
 RTC_EXPORT int rtcDeleteDataChannel(int dc);
 RTC_EXPORT int rtcDeleteDataChannel(int dc);
 
 
 RTC_EXPORT int rtcGetDataChannelStream(int dc);
 RTC_EXPORT int rtcGetDataChannelStream(int dc);
@@ -286,47 +301,41 @@ RTC_EXPORT int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *start
 // Start stats recording for RTCP Sender Reporter
 // Start stats recording for RTCP Sender Reporter
 RTC_EXPORT int rtcStartRtcpSenderReporterRecording(int id);
 RTC_EXPORT int rtcStartRtcpSenderReporterRecording(int id);
 
 
-// Transform seconds to timestamp using track's clock rate
-// Result is written to timestamp
+// Transform seconds to timestamp using track's clock rate, result is written to timestamp
 RTC_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
 RTC_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
 
 
-// Transform timestamp to seconds using track's clock rate
-// Result is written to seconds
+// Transform timestamp to seconds using track's clock rate, result is written to seconds
 RTC_EXPORT int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds);
 RTC_EXPORT int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds);
 
 
-// Get current timestamp
-// Result is written to timestamp
+// Get current timestamp, result is written to timestamp
 RTC_EXPORT int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp);
 RTC_EXPORT int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp);
 
 
-// Get start timestamp for track identified by given id
-// Result is written to timestamp
+// Get start timestamp for track identified by given id, result is written to timestamp
 RTC_EXPORT int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp);
 RTC_EXPORT int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp);
 
 
 // Set RTP timestamp for track identified by given id
 // Set RTP timestamp for track identified by given id
 RTC_EXPORT int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp);
 RTC_EXPORT int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp);
 
 
-// Get timestamp of previous RTCP SR
-// Result is written to timestamp
+// Get timestamp of previous RTCP SR, result is written to timestamp
 RTC_EXPORT int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp);
 RTC_EXPORT int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp);
 
 
 // Set NeedsToReport flag in RtcpSrReporter handler identified by given track id
 // Set NeedsToReport flag in RtcpSrReporter handler identified by given track id
 RTC_EXPORT int rtcSetNeedsToSendRtcpSr(int id);
 RTC_EXPORT int rtcSetNeedsToSendRtcpSr(int id);
 
 
-/// Get all available payload types for given codec and stores them in buffer, does nothing if
-/// buffer is NULL
+// Get all available payload types for given codec and stores them in buffer, does nothing if
+// buffer is NULL
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size);
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size);
 
 
-/// Get all SSRCs for given track
+// Get all SSRCs for given track
 int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count);
 int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count);
 
 
-/// Get CName for SSRC
+// Get CName for SSRC
 int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize);
 int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize);
 
 
-/// Get all SSRCs for given media type in given SDP
-/// @param mediaType Media type (audio/video)
+// Get all SSRCs for given media type in given SDP
 int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize);
 int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize);
 
 
-/// Set SSRC for given media type in given SDP
+// Set SSRC for given media type in given SDP
 int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
 int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
                       rtcSsrcForTypeInit *init);
                       rtcSsrcForTypeInit *init);
 
 
@@ -367,24 +376,6 @@ RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver);
 
 
 #endif
 #endif
 
 
-// DataChannel, Track, and WebSocket common API
-
-RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
-RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
-RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
-RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
-RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
-
-RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
-RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
-RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
-
-// DataChannel, Track, and WebSocket common extended API
-
-RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
-RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
-RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
-
 // Optional global preload and cleanup
 // Optional global preload and cleanup
 
 
 RTC_EXPORT void rtcPreload(void);
 RTC_EXPORT void rtcPreload(void);

+ 766 - 767
src/capi.cpp

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2019-2020 Paul-Louis Ageneau
+ * Copyright (c) 2019-2021 Paul-Louis Ageneau
  *
  *
  * This library is free software; you can redistribute it and/or
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * modify it under the terms of the GNU Lesser General Public
@@ -134,8 +134,75 @@ void eraseTrack(int tr) {
 	userPointerMap.erase(tr);
 	userPointerMap.erase(tr);
 }
 }
 
 
+shared_ptr<Channel> getChannel(int id) {
+	std::lock_guard lock(mutex);
+	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
+		return it->second;
+	if (auto it = trackMap.find(id); it != trackMap.end())
+		return it->second;
+#if RTC_ENABLE_WEBSOCKET
+	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
+		return it->second;
+#endif
+	throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist");
+}
+
+int copyAndReturn(string s, char *buffer, int size) {
+	if (!buffer)
+		return int(s.size() + 1);
+
+	if (size < int(s.size()))
+		return RTC_ERR_TOO_SMALL;
+
+	std::copy(s.begin(), s.end(), buffer);
+	buffer[s.size()] = '\0';
+	return int(s.size() + 1);
+}
+
+int copyAndReturn(binary b, char *buffer, int size) {
+	if (!buffer)
+		return int(b.size());
+
+	if (size < int(b.size()))
+		return RTC_ERR_TOO_SMALL;
+
+	auto data = reinterpret_cast<const char *>(b.data());
+	std::copy(data, data + b.size(), buffer);
+	buffer[b.size()] = '\0';
+	return int(b.size());
+}
+
+template <typename T> int copyAndReturn(std::vector<T> b, T *buffer, int size) {
+	if (!buffer)
+		return int(b.size());
+
+	if (size < int(b.size()))
+		return RTC_ERR_TOO_SMALL;
+	std::copy(b.begin(), b.end(), buffer);
+	return int(b.size());
+}
+
+template <typename F> int wrap(F func) {
+	try {
+		return int(func());
+
+	} catch (const std::invalid_argument &e) {
+		PLOG_ERROR << e.what();
+		return RTC_ERR_INVALID;
+	} catch (const std::exception &e) {
+		PLOG_ERROR << e.what();
+		return RTC_ERR_FAILURE;
+	}
+}
+
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
 
 
+string lowercased(string str) {
+	std::transform(str.begin(), str.end(), str.begin(),
+	               [](unsigned char c) { return std::tolower(c); });
+	return str;
+}
+
 shared_ptr<RtcpSrReporter> getRtcpSrReporter(int id) {
 shared_ptr<RtcpSrReporter> getRtcpSrReporter(int id) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	if (auto it = rtcpSrReporterMap.find(id); it != rtcpSrReporterMap.end()) {
 	if (auto it = rtcpSrReporterMap.find(id); it != rtcpSrReporterMap.end()) {
@@ -243,75 +310,6 @@ void eraseWebSocketServer(int wsserver) {
 
 
 #endif
 #endif
 
 
-shared_ptr<Channel> getChannel(int id) {
-	std::lock_guard lock(mutex);
-	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
-		return it->second;
-	if (auto it = trackMap.find(id); it != trackMap.end())
-		return it->second;
-#if RTC_ENABLE_WEBSOCKET
-	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
-		return it->second;
-#endif
-	throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist");
-}
-
-template <typename F> int wrap(F func) {
-	try {
-		return int(func());
-
-	} catch (const std::invalid_argument &e) {
-		PLOG_ERROR << e.what();
-		return RTC_ERR_INVALID;
-	} catch (const std::exception &e) {
-		PLOG_ERROR << e.what();
-		return RTC_ERR_FAILURE;
-	}
-}
-
-int copyAndReturn(string s, char *buffer, int size) {
-	if (!buffer)
-		return int(s.size() + 1);
-
-	if (size < int(s.size()))
-		return RTC_ERR_TOO_SMALL;
-
-	std::copy(s.begin(), s.end(), buffer);
-	buffer[s.size()] = '\0';
-	return int(s.size() + 1);
-}
-
-int copyAndReturn(binary b, char *buffer, int size) {
-	if (!buffer)
-		return int(b.size());
-
-	if (size < int(b.size()))
-		return RTC_ERR_TOO_SMALL;
-
-	auto data = reinterpret_cast<const char *>(b.data());
-	std::copy(data, data + b.size(), buffer);
-	buffer[b.size()] = '\0';
-	return int(b.size());
-}
-
-template <typename T> int copyAndReturn(std::vector<T> b, T *buffer, int size) {
-	if (!buffer)
-		return int(b.size());
-
-	if (size < int(b.size()))
-		return RTC_ERR_TOO_SMALL;
-	std::copy(b.begin(), b.end(), buffer);
-	return int(b.size());
-}
-
-#if RTC_ENABLE_MEDIA
-// function is used in RTC_ENABLE_MEDIA only
-string lowercased(string str) {
-	std::transform(str.begin(), str.end(), str.begin(),
-	               [](unsigned char c) { return std::tolower(c); });
-	return str;
-}
-#endif // RTC_ENABLE_MEDIA
 } // namespace
 } // namespace
 
 
 void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
 void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
@@ -372,968 +370,969 @@ int rtcDeletePeerConnection(int pc) {
 	});
 	});
 }
 }
 
 
-int rtcCreateDataChannel(int pc, const char *label) {
-	return rtcCreateDataChannelEx(pc, label, nullptr);
+int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onLocalDescription([pc, cb](Description desc) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
+			});
+		else
+			peerConnection->onLocalDescription(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
 }
 }
 
 
-int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
+int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		DataChannelInit dci = {};
-		if (init) {
-			auto *reliability = &init->reliability;
-			dci.reliability.unordered = reliability->unordered;
-			if (reliability->unreliable) {
-				if (reliability->maxPacketLifeTime > 0) {
-					dci.reliability.type = Reliability::Type::Timed;
-					dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
-				} else {
-					dci.reliability.type = Reliability::Type::Rexmit;
-					dci.reliability.rexmit = reliability->maxRetransmits;
-				}
-			} else {
-				dci.reliability.type = Reliability::Type::Reliable;
-			}
-
-			dci.negotiated = init->negotiated;
-			dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt;
-			dci.protocol = init->protocol ? init->protocol : "";
-		}
-
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
-		int dc = emplaceDataChannel(
-		    peerConnection->createDataChannel(string(label ? label : ""), std::move(dci)));
-
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(dc, *ptr);
-
-		return dc;
+		if (cb)
+			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
+			});
+		else
+			peerConnection->onLocalCandidate(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcIsOpen(int cid) {
-	return wrap([cid] { return getChannel(cid)->isOpen(); });
+int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcState>(state), *ptr);
+			});
+		else
+			peerConnection->onStateChange(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
 }
 }
 
 
-int rtcDeleteDataChannel(int dc) {
-	return wrap([dc] {
-		auto dataChannel = getDataChannel(dc);
-		dataChannel->onOpen(nullptr);
-		dataChannel->onClosed(nullptr);
-		dataChannel->onError(nullptr);
-		dataChannel->onMessage(nullptr);
-		dataChannel->onBufferedAmountLow(nullptr);
-		dataChannel->onAvailable(nullptr);
-
-		eraseDataChannel(dc);
+int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcGatheringState>(state), *ptr);
+			});
+		else
+			peerConnection->onGatheringStateChange(nullptr);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
+int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		if (!mediaDescriptionSdp)
-			throw std::invalid_argument("Unexpected null pointer for track media description");
-
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
-		Description::Media media{string(mediaDescriptionSdp)};
-		int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(tr, *ptr);
-
-		return tr;
+		if (cb)
+			peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcSignalingState>(state), *ptr);
+			});
+		else
+			peerConnection->onGatheringStateChange(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
+int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onDataChannel([pc, cb](shared_ptr<DataChannel> dataChannel) {
+				int dc = emplaceDataChannel(dataChannel);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(dc, *ptr);
+					cb(pc, dc, *ptr);
+				}
+			});
+		else
+			peerConnection->onDataChannel(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
 
-		if (!init)
-			throw std::invalid_argument("Unexpected null pointer for track init");
-
-		auto direction = static_cast<Description::Direction>(init->direction);
-
-		string mid;
-		if (init->mid) {
-			mid = string(init->mid);
-		} else {
-			switch (init->codec) {
-			case RTC_CODEC_H264:
-			case RTC_CODEC_VP8:
-			case RTC_CODEC_VP9:
-				mid = "video";
-				break;
-			case RTC_CODEC_OPUS:
-				mid = "audio";
-				break;
-			default:
-				mid = "video";
-				break;
-			}
-		}
-
-		optional<Description::Media> optDescription = nullopt;
-
-		switch (init->codec) {
-		case RTC_CODEC_H264:
-		case RTC_CODEC_VP8:
-		case RTC_CODEC_VP9: {
-			auto desc = Description::Video(mid, direction);
-			switch (init->codec) {
-			case RTC_CODEC_H264:
-				desc.addH264Codec(init->payloadType);
-				break;
-			case RTC_CODEC_VP8:
-				desc.addVP8Codec(init->payloadType);
-				break;
-			case RTC_CODEC_VP9:
-				desc.addVP8Codec(init->payloadType);
-				break;
-			default:
-				break;
-			}
-			optDescription = desc;
-			break;
-		}
-		case RTC_CODEC_OPUS: {
-			auto desc = Description::Audio(mid, direction);
-			switch (init->codec) {
-			case RTC_CODEC_OPUS:
-				desc.addOpusCodec(init->payloadType);
-				break;
-			default:
-				break;
-			}
-			optDescription = desc;
-			break;
-		}
-		default:
-			break;
-		}
-
-		if (!optDescription)
-			throw std::invalid_argument("Unexpected codec");
-
-		auto desc = std::move(*optDescription);
-		desc.addSSRC(init->ssrc, init->name ? std::make_optional(string(init->name)) : nullopt,
-		             init->msid ? std::make_optional(string(init->msid)) : nullopt,
-		             init->trackId ? std::make_optional(string(init->trackId)) : nullopt);
-
-		int tr = emplaceTrack(peerConnection->addTrack(std::move(desc)));
-
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(tr, *ptr);
-
-		return tr;
-	});
-}
-
-int rtcDeleteTrack(int tr) {
+int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		track->onOpen(nullptr);
-		track->onClosed(nullptr);
-		track->onError(nullptr);
-		track->onMessage(nullptr);
-		track->onBufferedAmountLow(nullptr);
-		track->onAvailable(nullptr);
-
-		eraseTrack(tr);
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onTrack([pc, cb](shared_ptr<Track> track) {
+				int tr = emplaceTrack(track);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(tr, *ptr);
+					cb(pc, tr, *ptr);
+				}
+			});
+		else
+			peerConnection->onTrack(nullptr);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetTrackDescription(int tr, char *buffer, int size) {
+int rtcSetLocalDescription(int pc, const char *type) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		return copyAndReturn(track->description(), buffer, size);
+		auto peerConnection = getPeerConnection(pc);
+		peerConnection->setLocalDescription(type ? Description::stringToType(type)
+		                                         : Description::Type::Unspec);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-#if RTC_ENABLE_MEDIA
-
-void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid,
-             const char *_trackID) {
-
-	optional<string> name = nullopt;
-	if (_name) {
-		name = string(_name);
-	}
-
-	optional<string> msid = nullopt;
-	if (_msid) {
-		msid = string(_msid);
-	}
-
-	optional<string> trackID = nullopt;
-	if (_trackID) {
-		trackID = string(_trackID);
-	}
+int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
 
 
-	description->addSSRC(ssrc, name, msid, trackID);
-}
+		if (!sdp)
+			throw std::invalid_argument("Unexpected null pointer for remote description");
 
 
-int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
-	return wrap([&] {
-		auto track = getTrack(tr);
-		// create RTP configuration
-		auto rtpConfig = createRtpPacketizationConfig(init);
-		// create packetizer
-		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
-		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
-		auto packetizer = std::make_shared<H264RtpPacketizer>(rtpConfig, maxFragmentSize);
-		// create H264 handler
-		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
-		emplaceMediaChainableHandler(h264Handler, tr);
-		emplaceRtpConfig(rtpConfig, tr);
-		// set handler
-		track->setMediaHandler(h264Handler);
+		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
+int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		// create RTP configuration
-		auto rtpConfig = createRtpPacketizationConfig(init);
-		// create packetizer
-		auto packetizer = std::make_shared<OpusRtpPacketizer>(rtpConfig);
-		// create Opus handler
-		auto opusHandler = std::make_shared<OpusPacketizationHandler>(packetizer);
-		emplaceMediaChainableHandler(opusHandler, tr);
-		emplaceRtpConfig(rtpConfig, tr);
-		// set handler
-		track->setMediaHandler(opusHandler);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
 
-int rtcChainRtcpSrReporter(int tr) {
-	return wrap([tr] {
-		auto config = getRtpConfig(tr);
-		auto reporter = std::make_shared<RtcpSrReporter>(config);
-		emplaceRtcpSrReporter(reporter, tr);
-		auto chainableHandler = getMediaChainableHandler(tr);
-		chainableHandler->addToChain(reporter);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		if (!cand)
+			throw std::invalid_argument("Unexpected null pointer for remote candidate");
 
 
-int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
-	return wrap([tr, maxStoredPacketsCount] {
-		auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
-		auto chainableHandler = getMediaChainableHandler(tr);
-		chainableHandler->addToChain(responder);
+		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *startTime) {
+int rtcGetLocalDescription(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		auto epoch = startTime->since1970 ? RtpPacketizationConfig::EpochStart::T1970
-		                                  : RtpPacketizationConfig::EpochStart::T1900;
-		config->setStartTime(startTime->seconds, epoch, startTime->timestamp);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
 
-int rtcStartRtcpSenderReporterRecording(int id) {
-	return wrap([id] {
-		auto sender = getRtcpSrReporter(id);
-		sender->startRecording();
-		return RTC_ERR_SUCCESS;
+		if (auto desc = peerConnection->localDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
+int rtcGetRemoteDescription(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->secondsToTimestamp(seconds);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
 
-int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds) {
-	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*seconds = config->timestampToSeconds(timestamp);
-		return RTC_ERR_SUCCESS;
+		if (auto desc = peerConnection->remoteDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp) {
+int rtcGetLocalDescriptionType(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->timestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto desc = peerConnection->localDescription())
+			return copyAndReturn(desc->typeString(), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp) {
+int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->startTimestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto desc = peerConnection->remoteDescription())
+			return copyAndReturn(desc->typeString(), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp) {
+int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		config->timestamp = timestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto addr = peerConnection->localAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp) {
+int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto sender = getRtcpSrReporter(id);
-		*timestamp = sender->previousReportedTimestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto addr = peerConnection->remoteAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcSetNeedsToSendRtcpSr(int id) {
-	return wrap([id] {
-		auto sender = getRtcpSrReporter(id);
-		sender->setNeedsToReport();
-		return RTC_ERR_SUCCESS;
+int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		Candidate localCand;
+		Candidate remoteCand;
+		if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
+			return RTC_ERR_NOT_AVAIL;
+
+		int localRet = copyAndReturn(string(localCand), local, localSize);
+		if (localRet < 0)
+			return localRet;
+
+		int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize);
+		if (remoteRet < 0)
+			return remoteRet;
+
+		return std::max(localRet, remoteRet);
 	});
 	});
 }
 }
 
 
-int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
+int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto codec = lowercased(string(ccodec));
-		auto description = track->description();
-		std::vector<int> payloadTypes{};
-		payloadTypes.reserve(std::max(size, 0));
-		for (auto it = description.beginMaps(); it != description.endMaps(); it++) {
-			auto element = *it;
-			if (lowercased(element.second.format) == codec) {
-				payloadTypes.push_back(element.first);
-			}
-		}
-		return copyAndReturn(payloadTypes, buffer, size);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onOpen([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onOpen(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count) {
+int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto ssrcs = track->description().getSSRCs();
-		return copyAndReturn(ssrcs, buffer, count);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onClosed([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onClosed(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize) {
+int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto description = track->description();
-		auto optCName = description.getCNameForSsrc(ssrc);
-		if (optCName.has_value()) {
-			return copyAndReturn(optCName.value(), cname, cnameSize);
-		} else {
-			return 0;
-		}
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onError([id, cb](string error) {
+				if (auto ptr = getUserPointer(id))
+					cb(id, error.c_str(), *ptr);
+			});
+		else
+			channel->onError(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize) {
+int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto type = lowercased(string(mediaType));
-		auto oldSDP = string(sdp);
-		auto description = Description(oldSDP, "unspec");
-		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
-			if (std::holds_alternative<Description::Media *>(description.media(i))) {
-				auto media = std::get<Description::Media *>(description.media(i));
-				auto currentMediaType = lowercased(media->type());
-				if (currentMediaType == type) {
-					auto ssrcs = media->getSSRCs();
-					return copyAndReturn(ssrcs, buffer, bufferSize);
-				}
-			}
-		}
-		return 0;
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onMessage(
+			    [id, cb](binary b) {
+				    if (auto ptr = getUserPointer(id))
+					    cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
+			    },
+			    [id, cb](string s) {
+				    if (auto ptr = getUserPointer(id))
+					    cb(id, s.c_str(), -int(s.size() + 1), *ptr);
+			    });
+		else
+			channel->onMessage(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
-                      rtcSsrcForTypeInit *init) {
+int rtcSendMessage(int id, const char *data, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto type = lowercased(string(mediaType));
-		auto prevSDP = string(sdp);
-		auto description = Description(prevSDP, "unspec");
-		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
-			if (std::holds_alternative<Description::Media *>(description.media(i))) {
-				auto media = std::get<Description::Media *>(description.media(i));
-				auto currentMediaType = lowercased(media->type());
-				if (currentMediaType == type) {
-					setSSRC(media, init->ssrc, init->name, init->msid, init->trackId);
-					break;
-				}
-			}
+		auto channel = getChannel(id);
+
+		if (!data && size != 0)
+			throw std::invalid_argument("Unexpected null pointer for data");
+
+		if (size >= 0) {
+			auto b = reinterpret_cast<const byte *>(data);
+			channel->send(binary(b, b + size));
+			return size;
+		} else {
+			string str(data);
+			int len = int(str.size());
+			channel->send(std::move(str));
+			return len;
 		}
 		}
-		return copyAndReturn(string(description), buffer, bufferSize);
 	});
 	});
 }
 }
 
 
-#endif // RTC_ENABLE_MEDIA
-#if RTC_ENABLE_WEBSOCKET
+int rtcIsOpen(int id) {
+	return wrap([id] { return getChannel(id)->isOpen(); });
+}
 
 
-int rtcCreateWebSocket(const char *url) {
-	return wrap([&] {
-		auto webSocket = std::make_shared<WebSocket>();
-		webSocket->open(url);
-		return emplaceWebSocket(webSocket);
+int rtcGetBufferedAmount(int id) {
+	return wrap([id] {
+		auto channel = getChannel(id);
+		return int(channel->bufferedAmount());
 	});
 	});
 }
 }
 
 
-int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) {
+int rtcSetBufferedAmountLowThreshold(int id, int amount) {
 	return wrap([&] {
 	return wrap([&] {
-		if (!url)
-			throw std::invalid_argument("Unexpected null pointer for URL");
-
-		if (!config)
-			throw std::invalid_argument("Unexpected null pointer for config");
-
-		WebSocket::Configuration c;
-		c.disableTlsVerification = config->disableTlsVerification;
-		auto webSocket = std::make_shared<WebSocket>(std::move(c));
-		webSocket->open(url);
-		return emplaceWebSocket(webSocket);
+		auto channel = getChannel(id);
+		channel->setBufferedAmountLowThreshold(size_t(amount));
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcDeleteWebSocket(int ws) {
+int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		webSocket->onOpen(nullptr);
-		webSocket->onClosed(nullptr);
-		webSocket->onError(nullptr);
-		webSocket->onMessage(nullptr);
-		webSocket->onBufferedAmountLow(nullptr);
-		webSocket->onAvailable(nullptr);
-
-		eraseWebSocket(ws);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onBufferedAmountLow([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onBufferedAmountLow(nullptr);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size) {
+int rtcGetAvailableAmount(int id) {
+	return wrap([id] { return int(getChannel(id)->availableAmount()); });
+}
+
+int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		if (auto remoteAddress = webSocket->remoteAddress())
-			return copyAndReturn(*remoteAddress, buffer, size);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onAvailable([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
 		else
 		else
-			return RTC_ERR_NOT_AVAIL;
+			channel->onAvailable(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetWebSocketPath(int ws, char *buffer, int size) {
+int rtcReceiveMessage(int id, char *buffer, int *size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		if (auto path = webSocket->path())
-			return copyAndReturn(*path, buffer, size);
-		else
+		auto channel = getChannel(id);
+
+		if (!size)
+			throw std::invalid_argument("Unexpected null pointer for size");
+
+		*size = std::abs(*size);
+
+		auto message = channel->peek();
+		if (!message)
 			return RTC_ERR_NOT_AVAIL;
 			return RTC_ERR_NOT_AVAIL;
+
+		return std::visit( //
+		    overloaded{
+		        [&](binary b) {
+			        int ret = copyAndReturn(std::move(b), buffer, *size);
+			        if (ret >= 0) {
+				        channel->receive(); // discard
+				        *size = ret;
+				        return RTC_ERR_SUCCESS;
+			        } else {
+				        *size = int(b.size());
+				        return ret;
+			        }
+		        },
+		        [&](string s) {
+			        int ret = copyAndReturn(std::move(s), buffer, *size);
+			        if (ret >= 0) {
+				        channel->receive(); // discard
+				        *size = -ret;
+				        return RTC_ERR_SUCCESS;
+			        } else {
+				        *size = -int(s.size() + 1);
+				        return ret;
+			        }
+		        },
+		    },
+		    *message);
 	});
 	});
 }
 }
 
 
-RTC_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
-                                        rtcWebSocketClientCallbackFunc cb) {
+int rtcCreateDataChannel(int pc, const char *label) {
+	return rtcCreateDataChannelEx(pc, label, nullptr);
+}
+
+int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
 	return wrap([&] {
 	return wrap([&] {
-		if (!config)
-			throw std::invalid_argument("Unexpected null pointer for config");
+		DataChannelInit dci = {};
+		if (init) {
+			auto *reliability = &init->reliability;
+			dci.reliability.unordered = reliability->unordered;
+			if (reliability->unreliable) {
+				if (reliability->maxPacketLifeTime > 0) {
+					dci.reliability.type = Reliability::Type::Timed;
+					dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
+				} else {
+					dci.reliability.type = Reliability::Type::Rexmit;
+					dci.reliability.rexmit = reliability->maxRetransmits;
+				}
+			} else {
+				dci.reliability.type = Reliability::Type::Reliable;
+			}
 
 
-		if (!cb)
-			throw std::invalid_argument("Unexpected null pointer for client callback");
+			dci.negotiated = init->negotiated;
+			dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt;
+			dci.protocol = init->protocol ? init->protocol : "";
+		}
 
 
-		WebSocketServer::Configuration c;
-		c.port = config->port;
-		c.enableTls = config->enableTls;
-		c.certificatePemFile = config->certificatePemFile
-		                           ? make_optional(string(config->certificatePemFile))
-		                           : nullopt;
-		c.keyPemFile = config->keyPemFile ? make_optional(string(config->keyPemFile)) : nullopt;
-		c.keyPemPass = config->keyPemPass ? make_optional(string(config->keyPemPass)) : nullopt;
-		auto webSocketServer = std::make_shared<WebSocketServer>(std::move(c));
-		int wsserver = emplaceWebSocketServer(webSocketServer);
+		auto peerConnection = getPeerConnection(pc);
+		int dc = emplaceDataChannel(
+		    peerConnection->createDataChannel(string(label ? label : ""), std::move(dci)));
 
 
-		webSocketServer->onClient([wsserver, cb](shared_ptr<WebSocket> webSocket) {
-			int ws = emplaceWebSocket(webSocket);
-			if (auto ptr = getUserPointer(wsserver)) {
-				rtcSetUserPointer(wsserver, *ptr);
-				cb(wsserver, ws, *ptr);
-			}
-		});
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(dc, *ptr);
 
 
-		return wsserver;
+		return dc;
 	});
 	});
 }
 }
 
 
-RTC_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
-	return wrap([&] {
-		auto webSocketServer = getWebSocketServer(wsserver);
-		webSocketServer->onClient(nullptr);
-		webSocketServer->stop();
+int rtcDeleteDataChannel(int dc) {
+	return wrap([dc] {
+		auto dataChannel = getDataChannel(dc);
+		dataChannel->onOpen(nullptr);
+		dataChannel->onClosed(nullptr);
+		dataChannel->onError(nullptr);
+		dataChannel->onMessage(nullptr);
+		dataChannel->onBufferedAmountLow(nullptr);
+		dataChannel->onAvailable(nullptr);
 
 
-		eraseWebSocketServer(wsserver);
+		eraseDataChannel(dc);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver) {
-	return wrap([&] {
-		auto webSocketServer = getWebSocketServer(wsserver);
-		return int(webSocketServer->port());
+int rtcGetDataChannelStream(int dc) {
+	return wrap([dc] {
+		auto dataChannel = getDataChannel(dc);
+		return int(dataChannel->id());
 	});
 	});
 }
 }
 
 
-#endif
-
-int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
+int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onLocalDescription([pc, cb](Description desc) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
-			});
-		else
-			peerConnection->onLocalDescription(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto dataChannel = getDataChannel(dc);
+		return copyAndReturn(dataChannel->label(), buffer, size);
 	});
 	});
 }
 }
 
 
-int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
+int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
-			});
-		else
-			peerConnection->onLocalCandidate(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto dataChannel = getDataChannel(dc);
+		return copyAndReturn(dataChannel->protocol(), buffer, size);
 	});
 	});
 }
 }
 
 
-int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
+int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, static_cast<rtcState>(state), *ptr);
-			});
-		else
-			peerConnection->onStateChange(nullptr);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto dataChannel = getDataChannel(dc);
 
 
-int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, static_cast<rtcGatheringState>(state), *ptr);
-			});
-		else
-			peerConnection->onGatheringStateChange(nullptr);
+		if (!reliability)
+			throw std::invalid_argument("Unexpected null pointer for reliability");
+
+		Reliability dcr = dataChannel->reliability();
+		std::memset(reliability, 0, sizeof(*reliability));
+		reliability->unordered = dcr.unordered;
+		if (dcr.type == Reliability::Type::Timed) {
+			reliability->unreliable = true;
+			reliability->maxPacketLifeTime = int(std::get<milliseconds>(dcr.rexmit).count());
+		} else if (dcr.type == Reliability::Type::Rexmit) {
+			reliability->unreliable = true;
+			reliability->maxRetransmits = std::get<int>(dcr.rexmit);
+		} else {
+			reliability->unreliable = false;
+		}
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
+int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
 	return wrap([&] {
 	return wrap([&] {
+		if (!mediaDescriptionSdp)
+			throw std::invalid_argument("Unexpected null pointer for track media description");
+
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, static_cast<rtcSignalingState>(state), *ptr);
-			});
-		else
-			peerConnection->onGatheringStateChange(nullptr);
-		return RTC_ERR_SUCCESS;
+		Description::Media media{string(mediaDescriptionSdp)};
+		int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(tr, *ptr);
+
+		return tr;
 	});
 	});
 }
 }
 
 
-int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
+int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
 	return wrap([&] {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onDataChannel([pc, cb](shared_ptr<DataChannel> dataChannel) {
-				int dc = emplaceDataChannel(dataChannel);
-				if (auto ptr = getUserPointer(pc)) {
-					rtcSetUserPointer(dc, *ptr);
-					cb(pc, dc, *ptr);
-				}
-			});
-		else
-			peerConnection->onDataChannel(nullptr);
-		return RTC_ERR_SUCCESS;
+
+		if (!init)
+			throw std::invalid_argument("Unexpected null pointer for track init");
+
+		auto direction = static_cast<Description::Direction>(init->direction);
+
+		string mid;
+		if (init->mid) {
+			mid = string(init->mid);
+		} else {
+			switch (init->codec) {
+			case RTC_CODEC_H264:
+			case RTC_CODEC_VP8:
+			case RTC_CODEC_VP9:
+				mid = "video";
+				break;
+			case RTC_CODEC_OPUS:
+				mid = "audio";
+				break;
+			default:
+				mid = "video";
+				break;
+			}
+		}
+
+		optional<Description::Media> optDescription = nullopt;
+
+		switch (init->codec) {
+		case RTC_CODEC_H264:
+		case RTC_CODEC_VP8:
+		case RTC_CODEC_VP9: {
+			auto desc = Description::Video(mid, direction);
+			switch (init->codec) {
+			case RTC_CODEC_H264:
+				desc.addH264Codec(init->payloadType);
+				break;
+			case RTC_CODEC_VP8:
+				desc.addVP8Codec(init->payloadType);
+				break;
+			case RTC_CODEC_VP9:
+				desc.addVP8Codec(init->payloadType);
+				break;
+			default:
+				break;
+			}
+			optDescription = desc;
+			break;
+		}
+		case RTC_CODEC_OPUS: {
+			auto desc = Description::Audio(mid, direction);
+			switch (init->codec) {
+			case RTC_CODEC_OPUS:
+				desc.addOpusCodec(init->payloadType);
+				break;
+			default:
+				break;
+			}
+			optDescription = desc;
+			break;
+		}
+		default:
+			break;
+		}
+
+		if (!optDescription)
+			throw std::invalid_argument("Unexpected codec");
+
+		auto desc = std::move(*optDescription);
+		desc.addSSRC(init->ssrc, init->name ? std::make_optional(string(init->name)) : nullopt,
+		             init->msid ? std::make_optional(string(init->msid)) : nullopt,
+		             init->trackId ? std::make_optional(string(init->trackId)) : nullopt);
+
+		int tr = emplaceTrack(peerConnection->addTrack(std::move(desc)));
+
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(tr, *ptr);
+
+		return tr;
 	});
 	});
 }
 }
 
 
-int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
+int rtcDeleteTrack(int tr) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onTrack([pc, cb](shared_ptr<Track> track) {
-				int tr = emplaceTrack(track);
-				if (auto ptr = getUserPointer(pc)) {
-					rtcSetUserPointer(tr, *ptr);
-					cb(pc, tr, *ptr);
-				}
-			});
-		else
-			peerConnection->onTrack(nullptr);
+		auto track = getTrack(tr);
+		track->onOpen(nullptr);
+		track->onClosed(nullptr);
+		track->onError(nullptr);
+		track->onMessage(nullptr);
+		track->onBufferedAmountLow(nullptr);
+		track->onAvailable(nullptr);
+
+		eraseTrack(tr);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetLocalDescription(int pc, const char *type) {
+int rtcGetTrackDescription(int tr, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		peerConnection->setLocalDescription(type ? Description::stringToType(type)
-		                                         : Description::Type::Unspec);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		return copyAndReturn(track->description(), buffer, size);
 	});
 	});
 }
 }
 
 
-int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+#if RTC_ENABLE_MEDIA
 
 
-		if (!sdp)
-			throw std::invalid_argument("Unexpected null pointer for remote description");
+void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid,
+             const char *_trackID) {
 
 
-		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
-		return RTC_ERR_SUCCESS;
-	});
-}
+	optional<string> name = nullopt;
+	if (_name) {
+		name = string(_name);
+	}
 
 
-int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+	optional<string> msid = nullopt;
+	if (_msid) {
+		msid = string(_msid);
+	}
 
 
-		if (!cand)
-			throw std::invalid_argument("Unexpected null pointer for remote candidate");
+	optional<string> trackID = nullopt;
+	if (_trackID) {
+		trackID = string(_trackID);
+	}
 
 
-		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
+	description->addSSRC(ssrc, name, msid, trackID);
+}
+
+int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
+	return wrap([&] {
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		// create packetizer
+		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
+		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
+		auto packetizer = std::make_shared<H264RtpPacketizer>(rtpConfig, maxFragmentSize);
+		// create H264 handler
+		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
+		emplaceMediaChainableHandler(h264Handler, tr);
+		emplaceRtpConfig(rtpConfig, tr);
+		// set handler
+		track->setMediaHandler(h264Handler);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetLocalDescription(int pc, char *buffer, int size) {
+int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto desc = peerConnection->localDescription())
-			return copyAndReturn(string(*desc), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		// create packetizer
+		auto packetizer = std::make_shared<OpusRtpPacketizer>(rtpConfig);
+		// create Opus handler
+		auto opusHandler = std::make_shared<OpusPacketizationHandler>(packetizer);
+		emplaceMediaChainableHandler(opusHandler, tr);
+		emplaceRtpConfig(rtpConfig, tr);
+		// set handler
+		track->setMediaHandler(opusHandler);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetRemoteDescription(int pc, char *buffer, int size) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+int rtcChainRtcpSrReporter(int tr) {
+	return wrap([tr] {
+		auto config = getRtpConfig(tr);
+		auto reporter = std::make_shared<RtcpSrReporter>(config);
+		emplaceRtcpSrReporter(reporter, tr);
+		auto chainableHandler = getMediaChainableHandler(tr);
+		chainableHandler->addToChain(reporter);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
 
-		if (auto desc = peerConnection->remoteDescription())
-			return copyAndReturn(string(*desc), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
+	return wrap([tr, maxStoredPacketsCount] {
+		auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
+		auto chainableHandler = getMediaChainableHandler(tr);
+		chainableHandler->addToChain(responder);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetLocalDescriptionType(int pc, char *buffer, int size) {
+int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *startTime) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+		auto config = getRtpConfig(id);
+		auto epoch = startTime->since1970 ? RtpPacketizationConfig::EpochStart::T1970
+		                                  : RtpPacketizationConfig::EpochStart::T1900;
+		config->setStartTime(startTime->seconds, epoch, startTime->timestamp);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
 
-		if (auto desc = peerConnection->localDescription())
-			return copyAndReturn(desc->typeString(), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+int rtcStartRtcpSenderReporterRecording(int id) {
+	return wrap([id] {
+		auto sender = getRtcpSrReporter(id);
+		sender->startRecording();
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
+int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto desc = peerConnection->remoteDescription())
-			return copyAndReturn(desc->typeString(), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*timestamp = config->secondsToTimestamp(seconds);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetLocalAddress(int pc, char *buffer, int size) {
+int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto addr = peerConnection->localAddress())
-			return copyAndReturn(std::move(*addr), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*seconds = config->timestampToSeconds(timestamp);
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetRemoteAddress(int pc, char *buffer, int size) {
+int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto addr = peerConnection->remoteAddress())
-			return copyAndReturn(std::move(*addr), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*timestamp = config->timestamp;
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		Candidate localCand;
-		Candidate remoteCand;
-		if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
-			return RTC_ERR_NOT_AVAIL;
-
-		int localRet = copyAndReturn(string(localCand), local, localSize);
-		if (localRet < 0)
-			return localRet;
-
-		int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize);
-		if (remoteRet < 0)
-			return remoteRet;
-
-		return std::max(localRet, remoteRet);
+		auto config = getRtpConfig(id);
+		*timestamp = config->startTimestamp;
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetDataChannelStream(int dc) {
-	return wrap([dc] {
-		auto dataChannel = getDataChannel(dc);
-		return int(dataChannel->id());
+int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp) {
+	return wrap([&] {
+		auto config = getRtpConfig(id);
+		config->timestamp = timestamp;
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
+int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
 	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-		return copyAndReturn(dataChannel->label(), buffer, size);
+		auto sender = getRtcpSrReporter(id);
+		*timestamp = sender->previousReportedTimestamp;
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
-	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-		return copyAndReturn(dataChannel->protocol(), buffer, size);
+int rtcSetNeedsToSendRtcpSr(int id) {
+	return wrap([id] {
+		auto sender = getRtcpSrReporter(id);
+		sender->setNeedsToReport();
+		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
+int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-
-		if (!reliability)
-			throw std::invalid_argument("Unexpected null pointer for reliability");
-
-		Reliability dcr = dataChannel->reliability();
-		std::memset(reliability, 0, sizeof(*reliability));
-		reliability->unordered = dcr.unordered;
-		if (dcr.type == Reliability::Type::Timed) {
-			reliability->unreliable = true;
-			reliability->maxPacketLifeTime = int(std::get<milliseconds>(dcr.rexmit).count());
-		} else if (dcr.type == Reliability::Type::Rexmit) {
-			reliability->unreliable = true;
-			reliability->maxRetransmits = std::get<int>(dcr.rexmit);
-		} else {
-			reliability->unreliable = false;
+		auto track = getTrack(tr);
+		auto codec = lowercased(string(ccodec));
+		auto description = track->description();
+		std::vector<int> payloadTypes{};
+		payloadTypes.reserve(std::max(size, 0));
+		for (auto it = description.beginMaps(); it != description.endMaps(); it++) {
+			auto element = *it;
+			if (lowercased(element.second.format) == codec) {
+				payloadTypes.push_back(element.first);
+			}
 		}
 		}
-		return RTC_ERR_SUCCESS;
+		return copyAndReturn(payloadTypes, buffer, size);
 	});
 	});
 }
 }
 
 
-int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
+int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onOpen([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
-		else
-			channel->onOpen(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		auto ssrcs = track->description().getSSRCs();
+		return copyAndReturn(ssrcs, buffer, count);
 	});
 	});
 }
 }
 
 
-int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
+int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onClosed([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
-		else
-			channel->onClosed(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		auto description = track->description();
+		auto optCName = description.getCNameForSsrc(ssrc);
+		if (optCName.has_value()) {
+			return copyAndReturn(optCName.value(), cname, cnameSize);
+		} else {
+			return 0;
+		}
 	});
 	});
 }
 }
 
 
-int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
+int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onError([id, cb](string error) {
-				if (auto ptr = getUserPointer(id))
-					cb(id, error.c_str(), *ptr);
-			});
-		else
-			channel->onError(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto type = lowercased(string(mediaType));
+		auto oldSDP = string(sdp);
+		auto description = Description(oldSDP, "unspec");
+		auto mediaCount = description.mediaCount();
+		for (unsigned int i = 0; i < mediaCount; i++) {
+			if (std::holds_alternative<Description::Media *>(description.media(i))) {
+				auto media = std::get<Description::Media *>(description.media(i));
+				auto currentMediaType = lowercased(media->type());
+				if (currentMediaType == type) {
+					auto ssrcs = media->getSSRCs();
+					return copyAndReturn(ssrcs, buffer, bufferSize);
+				}
+			}
+		}
+		return 0;
 	});
 	});
 }
 }
 
 
-int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
+int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
+                      rtcSsrcForTypeInit *init) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onMessage(
-			    [id, cb](binary b) {
-				    if (auto ptr = getUserPointer(id))
-					    cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
-			    },
-			    [id, cb](string s) {
-				    if (auto ptr = getUserPointer(id))
-					    cb(id, s.c_str(), -int(s.size() + 1), *ptr);
-			    });
-		else
-			channel->onMessage(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto type = lowercased(string(mediaType));
+		auto prevSDP = string(sdp);
+		auto description = Description(prevSDP, "unspec");
+		auto mediaCount = description.mediaCount();
+		for (unsigned int i = 0; i < mediaCount; i++) {
+			if (std::holds_alternative<Description::Media *>(description.media(i))) {
+				auto media = std::get<Description::Media *>(description.media(i));
+				auto currentMediaType = lowercased(media->type());
+				if (currentMediaType == type) {
+					setSSRC(media, init->ssrc, init->name, init->msid, init->trackId);
+					break;
+				}
+			}
+		}
+		return copyAndReturn(string(description), buffer, bufferSize);
 	});
 	});
 }
 }
 
 
-int rtcSendMessage(int id, const char *data, int size) {
-	return wrap([&] {
-		auto channel = getChannel(id);
+#endif // RTC_ENABLE_MEDIA
 
 
-		if (!data && size != 0)
-			throw std::invalid_argument("Unexpected null pointer for data");
+#if RTC_ENABLE_WEBSOCKET
 
 
-		if (size >= 0) {
-			auto b = reinterpret_cast<const byte *>(data);
-			channel->send(binary(b, b + size));
-			return size;
-		} else {
-			string str(data);
-			int len = int(str.size());
-			channel->send(std::move(str));
-			return len;
-		}
+int rtcCreateWebSocket(const char *url) {
+	return wrap([&] {
+		auto webSocket = std::make_shared<WebSocket>();
+		webSocket->open(url);
+		return emplaceWebSocket(webSocket);
 	});
 	});
 }
 }
 
 
-int rtcGetBufferedAmount(int id) {
-	return wrap([id] {
-		auto channel = getChannel(id);
-		return int(channel->bufferedAmount());
+int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) {
+	return wrap([&] {
+		if (!url)
+			throw std::invalid_argument("Unexpected null pointer for URL");
+
+		if (!config)
+			throw std::invalid_argument("Unexpected null pointer for config");
+
+		WebSocket::Configuration c;
+		c.disableTlsVerification = config->disableTlsVerification;
+		auto webSocket = std::make_shared<WebSocket>(std::move(c));
+		webSocket->open(url);
+		return emplaceWebSocket(webSocket);
 	});
 	});
 }
 }
 
 
-int rtcSetBufferedAmountLowThreshold(int id, int amount) {
+int rtcDeleteWebSocket(int ws) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		channel->setBufferedAmountLowThreshold(size_t(amount));
+		auto webSocket = getWebSocket(ws);
+		webSocket->onOpen(nullptr);
+		webSocket->onClosed(nullptr);
+		webSocket->onError(nullptr);
+		webSocket->onMessage(nullptr);
+		webSocket->onBufferedAmountLow(nullptr);
+		webSocket->onAvailable(nullptr);
+
+		eraseWebSocket(ws);
 		return RTC_ERR_SUCCESS;
 		return RTC_ERR_SUCCESS;
 	});
 	});
 }
 }
 
 
-int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
+int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onBufferedAmountLow([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
+		auto webSocket = getWebSocket(ws);
+		if (auto remoteAddress = webSocket->remoteAddress())
+			return copyAndReturn(*remoteAddress, buffer, size);
 		else
 		else
-			channel->onBufferedAmountLow(nullptr);
-		return RTC_ERR_SUCCESS;
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcGetAvailableAmount(int id) {
-	return wrap([id] { return int(getChannel(id)->availableAmount()); });
-}
-
-int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
+int rtcGetWebSocketPath(int ws, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onAvailable([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
+		auto webSocket = getWebSocket(ws);
+		if (auto path = webSocket->path())
+			return copyAndReturn(*path, buffer, size);
 		else
 		else
-			channel->onAvailable(nullptr);
-		return RTC_ERR_SUCCESS;
+			return RTC_ERR_NOT_AVAIL;
 	});
 	});
 }
 }
 
 
-int rtcReceiveMessage(int id, char *buffer, int *size) {
+RTC_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
+                                        rtcWebSocketClientCallbackFunc cb) {
 	return wrap([&] {
 	return wrap([&] {
-		auto channel = getChannel(id);
+		if (!config)
+			throw std::invalid_argument("Unexpected null pointer for config");
 
 
-		if (!size)
-			throw std::invalid_argument("Unexpected null pointer for size");
+		if (!cb)
+			throw std::invalid_argument("Unexpected null pointer for client callback");
 
 
-		*size = std::abs(*size);
+		WebSocketServer::Configuration c;
+		c.port = config->port;
+		c.enableTls = config->enableTls;
+		c.certificatePemFile = config->certificatePemFile
+		                           ? make_optional(string(config->certificatePemFile))
+		                           : nullopt;
+		c.keyPemFile = config->keyPemFile ? make_optional(string(config->keyPemFile)) : nullopt;
+		c.keyPemPass = config->keyPemPass ? make_optional(string(config->keyPemPass)) : nullopt;
+		auto webSocketServer = std::make_shared<WebSocketServer>(std::move(c));
+		int wsserver = emplaceWebSocketServer(webSocketServer);
 
 
-		auto message = channel->peek();
-		if (!message)
-			return RTC_ERR_NOT_AVAIL;
+		webSocketServer->onClient([wsserver, cb](shared_ptr<WebSocket> webSocket) {
+			int ws = emplaceWebSocket(webSocket);
+			if (auto ptr = getUserPointer(wsserver)) {
+				rtcSetUserPointer(wsserver, *ptr);
+				cb(wsserver, ws, *ptr);
+			}
+		});
 
 
-		return std::visit( //
-		    overloaded{
-		        [&](binary b) {
-			        int ret = copyAndReturn(std::move(b), buffer, *size);
-			        if (ret >= 0) {
-				        channel->receive(); // discard
-				        *size = ret;
-				        return RTC_ERR_SUCCESS;
-			        } else {
-				        *size = int(b.size());
-				        return ret;
-			        }
-		        },
-		        [&](string s) {
-			        int ret = copyAndReturn(std::move(s), buffer, *size);
-			        if (ret >= 0) {
-				        channel->receive(); // discard
-				        *size = -ret;
-				        return RTC_ERR_SUCCESS;
-			        } else {
-				        *size = -int(s.size() + 1);
-				        return ret;
-			        }
-		        },
-		    },
-		    *message);
+		return wsserver;
+	});
+}
+
+RTC_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
+	return wrap([&] {
+		auto webSocketServer = getWebSocketServer(wsserver);
+		webSocketServer->onClient(nullptr);
+		webSocketServer->stop();
+
+		eraseWebSocketServer(wsserver);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
+RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver) {
+	return wrap([&] {
+		auto webSocketServer = getWebSocketServer(wsserver);
+		return int(webSocketServer->port());
 	});
 	});
 }
 }
 
 
+#endif
+
 void rtcPreload() { rtc::Preload(); }
 void rtcPreload() { rtc::Preload(); }
 
 
 void rtcCleanup() { rtc::Cleanup(); }
 void rtcCleanup() { rtc::Cleanup(); }