/**
* @brief Called when an error occurs.
* @since_tizen 6.0
- * @remarks The @a webrtc is the same object for which the callback was set.
+ * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released.
* @param [in] webrtc WebRTC handle
* @param [in] error The error code
* @param [in] state The current state of the WebRTC handle
/**
* @brief Called when the WebRTC state is changed.
* @since_tizen 6.0
- * @remarks The @a webrtc is the same object for which the callback was set.
+ * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released.
* @param [in] webrtc WebRTC handle
* @param [in] previous The previous state of the WebRTC handle
* @param [in] current The current state of the WebRTC handle
/**
* @brief Called when the WebRTC needs session negotiation.
* @since_tizen 6.0
+ * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released.
* @param [in] webrtc WebRTC handle
* @param [in] user_data The user data passed from the callback registration function
* @see webrtc_set_negotiation_needed_cb()
*/
typedef void (*webrtc_negotiation_needed_cb)(webrtc_h webrtc, void *user_data);
+/**
+ * @brief Called when the WebRTC needs to send the ICE candidate message to the remote peer through the signaling channel.
+ * @since_tizen 6.0
+ * @remarks The @a webrtc is the same object for which the callback was set.\nThe @a webrtc should not be released.
+ * @param [in] webrtc WebRTC handle
+ * @param [in] candidate The ICE candidate message
+ * @param [in] user_data The user data passed from the callback registration function
+ * @see webrtc_set_ice_candidate_cb()
+ * @see webrtc_unset_ice_candidate_cb()
+ */
+typedef void (*webrtc_ice_candidate_cb)(webrtc_h webrtc, const char *candidate, void *user_data);
+
/**
* @brief Sets a callback function to be invoked when an asynchronous operation error occurs.
* @since_tizen 6.0
*/
int webrtc_unset_negotiation_needed_cb(webrtc_h webrtc);
+/**
+ * @brief Sets an ICE candidate callback function to be invoked when the WebRTC needs to send the ICE candidate message to the remote peer.
+ * @since_tizen 6.0
+ * @param [in] webrtc WebRTC handle
+ * @param [in] callback Callback function pointer
+ * @param [in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success,
+ * otherwise a negative error value
+ * @retval #WEBRTC_ERROR_NONE Successful
+ * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #WEBRTC_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #WEBRTC_ERROR_INVALID_STATE Invalid state
+ * @pre @a webrtc state must be set to #WEBRTC_STATE_IDLE.
+ * @post webrtc_ice_candidate_cb() will be invoked.
+ * @see webrtc_unset_ice_candidate_cb()
+ * @see webrtc_ice_candidate_cb()
+ */
+int webrtc_set_ice_candidate_cb(webrtc_h webrtc, webrtc_ice_candidate_cb callback, void *user_data);
+
+/**
+ * @brief Unsets the ice candidate callback function.
+ * @since_tizen 6.0
+ * @param [in] webrtc WebRTC handle
+ * @return @c 0 on success,
+ * otherwise a negative error value
+ * @retval #WEBRTC_ERROR_NONE Successful
+ * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #WEBRTC_ERROR_INVALID_OPERATION Invalid operation
+ * @see webrtc_set_ice_candidate_cb()
+ */
+int webrtc_unset_ice_candidate_cb(webrtc_h webrtc);
+
/**
* @brief Creates SDP offer to start a new WebRTC connection to a remote peer.
* @since_tizen 6.0
*/
int webrtc_set_remote_description(webrtc_h webrtc, const char *description);
+/**
+ * @brief Adds a new ICE candidate from the remote peer over its signaling channel.
+ * @since_tizen 6.0
+ * @remarks @a candidate is a JSON string.\n
+ * It will be {"ice":{"candidate":"..."}}.
+ * @param [in] webrtc WebRTC handle
+ * @param [in] candidate The ICE candidate
+ * @return @c 0 on success,
+ * otherwise a negative error value
+ * @retval #WEBRTC_ERROR_NONE Successful
+ * @retval #WEBRTC_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #WEBRTC_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #WEBRTC_ERROR_INVALID_STATE Invalid state
+ * @pre @a webrtc state must be set to #WEBRTC_STATE_PLAYING.
+ */
+int webrtc_add_ice_candidate(webrtc_h webrtc, const char *candidate);
+
/**
* @}
*/
webrtc_callbacks_s error_cb;
webrtc_callbacks_s state_changed_cb;
webrtc_callbacks_s negotiation_needed_cb;
+ webrtc_callbacks_s ice_candidate_cb;
} webrtc_s;
typedef struct _element_info_s {
int _webrtcbin_create_offer(webrtc_s *webrtc, char **offer);
int _webrtcbin_create_answer(webrtc_s *webrtc, char **answer);
int _webrtcbin_set_session_description(webrtc_s *webrtc, const char *description, gboolean is_remote);
+int _webrtcbin_add_ice_candidate(webrtc_s *webrtc, const char *candidate);
#ifdef __cplusplus
}
Name: capi-media-webrtc
Summary: A WebRTC library in Tizen Native API
-Version: 0.1.18
+Version: 0.1.19
Release: 0
Group: Multimedia/API
License: Apache-2.0
return WEBRTC_ERROR_NONE;
}
+int webrtc_set_ice_candidate_cb(webrtc_h webrtc, webrtc_ice_candidate_cb callback, void *user_data)
+{
+ webrtc_s *_webrtc = (webrtc_s*)webrtc;
+
+ RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+ RET_VAL_IF(callback == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+ g_mutex_lock(&_webrtc->mutex);
+
+ RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_IDLE, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be IDLE");
+
+ _webrtc->ice_candidate_cb.callback = callback;
+ _webrtc->ice_candidate_cb.user_data = user_data;
+
+ LOG_INFO("callback[%p] user_data[%p]", callback, user_data);
+
+ g_mutex_unlock(&_webrtc->mutex);
+
+ return WEBRTC_ERROR_NONE;
+}
+
+int webrtc_unset_ice_candidate_cb(webrtc_h webrtc)
+{
+ webrtc_s *_webrtc = (webrtc_s*)webrtc;
+
+ RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+
+ g_mutex_lock(&_webrtc->mutex);
+
+ RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_IDLE, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be IDLE");
+ RET_VAL_WITH_UNLOCK_IF(_webrtc->ice_candidate_cb.callback == NULL, WEBRTC_ERROR_INVALID_OPERATION, &_webrtc->mutex, "callback was not set");
+
+ LOG_INFO("callback[%p] user_data[%p] is reset to NULL",
+ _webrtc->ice_candidate_cb.callback, _webrtc->ice_candidate_cb.user_data);
+
+ _webrtc->ice_candidate_cb.callback = NULL;
+ _webrtc->ice_candidate_cb.user_data = NULL;
+
+ g_mutex_unlock(&_webrtc->mutex);
+
+ return WEBRTC_ERROR_NONE;
+}
+
int webrtc_create_offer(webrtc_h webrtc, char **offer)
{
int ret = WEBRTC_ERROR_NONE;
g_mutex_unlock(&_webrtc->mutex);
return ret;
-}
\ No newline at end of file
+}
+
+int webrtc_add_ice_candidate(webrtc_h webrtc, const char *candidate)
+{
+ int ret = WEBRTC_ERROR_NONE;
+ webrtc_s *_webrtc = (webrtc_s*)webrtc;
+
+ RET_VAL_IF(_webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+ RET_VAL_IF(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate is NULL");
+
+ g_mutex_lock(&_webrtc->mutex);
+
+ RET_VAL_WITH_UNLOCK_IF(_webrtc->state != WEBRTC_STATE_PLAYING, WEBRTC_ERROR_INVALID_STATE, &_webrtc->mutex, "the state should be PLAYING");
+
+ LOG_INFO("candidate[ %s ]", candidate);
+
+ ret = _webrtcbin_add_ice_candidate(webrtc, candidate);
+
+ g_mutex_unlock(&_webrtc->mutex);
+
+ return ret;
+}
} \
} while (0)
-/* Use g_free() to free the return value. */
+/* Use g_free() to release the return value. */
static gchar* __get_string_from_json_object(JsonObject *object)
{
JsonNode *root;
return text;
}
+/* Use g_free() to release the return value. */
static gchar* __make_sdp_message(GstWebRTCSessionDescription *desc)
{
gchar *text;
return text;
}
+/* Use g_free() to release the return value. */
+static gchar *__make_ice_candidate_message(guint mlineindex, gchar *candidate)
+{
+ JsonObject *ice, *msg;
+ gchar *text;
+
+ if (!candidate) {
+ LOG_DEBUG("candidate is NULL, all ICE candidates have been sent");
+ return NULL;
+ }
+
+ ice = json_object_new();
+ json_object_set_string_member(ice, "candidate", candidate);
+ json_object_set_int_member(ice, "sdpMLineIndex", mlineindex);
+
+ msg = json_object_new();
+ json_object_set_object_member(msg, "ice", ice);
+
+ text = __get_string_from_json_object(msg);
+
+ json_object_unref(msg);
+
+ return text;
+}
+
static gboolean __meet_gst_state(webrtc_state_e state, GstState gst_state)
{
if (state == WEBRTC_STATE_IDLE && gst_state == GST_STATE_READY)
LOG_DEBUG("<<< end of the callback");
}
+static void __webrtcbin_on_ice_candidate(GstElement *webrtcbin, guint mlineindex, gchar *candidate, gpointer user_data)
+{
+ webrtc_s *webrtc = (webrtc_s *)user_data;
+ gchar *_candidate;
+ char *_param_candidate;
+
+ RET_IF(webrtcbin == NULL, "webrtcbin is NULL");
+ RET_IF(webrtc == NULL, "webrtc is NULL");
+ RET_IF(webrtc->ice_candidate_cb.callback == NULL, "ice_candidate_cb is NULL");
+
+ _candidate = __make_ice_candidate_message(mlineindex, candidate);
+ if (!_candidate)
+ return;
+
+ _param_candidate = strdup(_candidate);
+ g_free(_candidate);
+
+ LOG_DEBUG(">>> invoke ice_candidate_cb[%p], user_data[%p]",
+ webrtc->ice_candidate_cb.callback, webrtc->ice_candidate_cb.user_data);
+ ((webrtc_ice_candidate_cb)(webrtc->ice_candidate_cb.callback))((webrtc_h)webrtc, (const char *)_param_candidate, webrtc->ice_candidate_cb.user_data);
+ LOG_DEBUG("<<< end of the callback");
+
+ free(_param_candidate);
+}
+
int _gst_build_pipeline(webrtc_s *webrtc)
{
RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
goto error;
}
__connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-negotiation-needed", G_CALLBACK(__webrtcbin_on_negotiation_needed), webrtc);
+ __connect_and_append_signal(&webrtc->signals, webrtc->gst.webrtcbin, "on-ice-candidate", G_CALLBACK(__webrtcbin_on_ice_candidate), webrtc);
if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), webrtc->gst.webrtcbin)) {
LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(webrtc->gst.webrtcbin), GST_ELEMENT_NAME(webrtc->gst.pipeline));
return _create_session_description(webrtc, FALSE, answer);
}
-/* Use g_free() to free the sdp and type parameter. */
+/* Use g_free() to release the sdp and type parameter. */
static int __get_sdp_from_description(const char *description, gchar **sdp, gchar **type)
{
int ret = WEBRTC_ERROR_NONE;
return ret;
}
+/* Use g_free() to release the candidate parameter. */
+static int __get_ice_candidate_from_message(const char *ice_message, gchar **candidate, gint *mlineindex)
+{
+ int ret = WEBRTC_ERROR_NONE;
+ JsonNode *root;
+ JsonObject *object;
+ JsonObject *child;
+ JsonParser *parser;
+ const gchar *_candidate;
+
+ RET_VAL_IF(ice_message == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ice_message is NULL");
+ RET_VAL_IF(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate is NULL");
+ RET_VAL_IF(mlineindex == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "mlineindex is NULL");
+
+ parser = json_parser_new();
+ if (!JSON_IS_PARSER(parser))
+ return WEBRTC_ERROR_INVALID_OPERATION;
+
+ if (!json_parser_load_from_data(parser, ice_message, -1, NULL)) {
+ LOG_ERROR("unknown message: %s", ice_message);
+ ret = WEBRTC_ERROR_INVALID_PARAMETER;
+ goto end;
+ }
+
+ root = json_parser_get_root(parser);
+ if (!JSON_NODE_HOLDS_OBJECT(root)) {
+ LOG_ERROR("it does not contain a JsonObject: %s", ice_message);
+ ret = WEBRTC_ERROR_INVALID_PARAMETER;
+ goto end;
+ }
+
+ object = json_node_get_object(root);
+ if (!json_object_has_member(object, "ice")) {
+ LOG_ERROR("It does not contain 'ice' member: %s", ice_message);
+ ret = WEBRTC_ERROR_INVALID_PARAMETER;
+ goto end;
+ }
+
+ child = json_object_get_object_member(object, "ice");
+
+ _candidate = json_object_get_string_member(child, "candidate");
+ if (!_candidate) {
+ LOG_ERROR("Could not find candidate member: %s", ice_message);
+ ret = WEBRTC_ERROR_INVALID_PARAMETER;
+ goto end;
+ }
+
+ *candidate = g_strdup(_candidate);
+ *mlineindex = json_object_get_int_member(child, "sdpMLineIndex");
+
+ LOG_DEBUG("candidate: %s", *candidate);
+ LOG_DEBUG("sdpMLineIndex: %d", *mlineindex);
+end:
+ g_object_unref(parser);
+ return ret;
+}
+
+int _webrtcbin_add_ice_candidate(webrtc_s *webrtc, const char *candidate)
+{
+ int ret = WEBRTC_ERROR_NONE;
+ gchar *_candidate;
+ gint sdpmlineindex;
+
+ RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL");
+ RET_VAL_IF(candidate == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "candidate is NULL");
+ RET_VAL_IF(webrtc->gst.webrtcbin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "webrtcbin is NULL");
+
+ ret = __get_ice_candidate_from_message(candidate, &_candidate, &sdpmlineindex);
+ if (ret != WEBRTC_ERROR_NONE)
+ return ret;
+
+ g_signal_emit_by_name(webrtc->gst.webrtcbin, "add-ice-candidate", sdpmlineindex, _candidate);
+
+ LOG_DEBUG("[add-ice-candidate] signal is emitted");
+
+ g_free(_candidate);
+
+ return ret;
+}
static char *g_offer;
static char *g_answer;
static char *g_remote_desc;
+static GList *g_ice_candidates;
static SoupWebsocketConnection *g_ws_conn;
static gint32 g_local_peer_id;
static gchar g_signaling_server[MAX_STRING_LEN];
g_print("webrtc_unset_negotiation_needed_cb() success\n");
}
+static void __ice_candidate_cb(webrtc_h webrtc, const char *candidate, void *user_data)
+{
+ g_print("__ice_candidate_cb() is invoked\n");
+
+ g_print("\n[to SERVER > ICE]\n%s\n", candidate);
+ soup_websocket_connection_send_text(g_ws_conn, candidate);
+}
+
+static void _webrtc_set_ice_candidate_cb()
+{
+ int ret = WEBRTC_ERROR_NONE;
+
+ ret = webrtc_set_ice_candidate_cb(g_webrtc, __ice_candidate_cb, g_webrtc);
+ if (ret != WEBRTC_ERROR_NONE)
+ g_print("failed to webrtc_set_ice_candidate_cb()\n");
+ else
+ g_print("webrtc_set_ice_candidate_cb() success\n");
+}
+
+static void _webrtc_unset_ice_candidate_cb()
+{
+ int ret = WEBRTC_ERROR_NONE;
+
+ ret = webrtc_unset_ice_candidate_cb(g_webrtc);
+ if (ret != WEBRTC_ERROR_NONE)
+ g_print("failed to webrtc_unset_ice_candidate_cb()\n");
+ else
+ g_print("webrtc_unset_ice_candidate_cb() success\n");
+}
+
static void _webrtc_create_offer()
{
int ret = WEBRTC_ERROR_NONE;
g_print("webrtc_set_remote_description() success\n");
}
+static void __foreach_ice_candidate(gpointer data, gpointer user_data)
+{
+ int ret = WEBRTC_ERROR_NONE;
+
+ ret = webrtc_add_ice_candidate(g_webrtc, (const char *)data);
+ if (ret != WEBRTC_ERROR_NONE)
+ g_print("failed to webrtc_ice_candidate()\n");
+ else
+ g_print("webrtc_ice_candidate() success\n");
+}
+
+static void _webrtc_add_ice_candidate(void)
+{
+ g_list_foreach(g_ice_candidates, __foreach_ice_candidate, NULL);
+
+ g_list_free_full(g_ice_candidates, free);
+}
+
static void _setting_uri(gchar *dest_arr, char *uri)
{
int ret = 0;
} else if (json_object_has_member(object, "ice")){
g_print("\n[from SERVER > ICE]\n%s\n", text);
- /* FIXME: add ice candidates, several candidates will arrive from the server */
+ g_ice_candidates = g_list_append(g_ice_candidates, strdup(text));
} else {
g_printerr("neither 'sdp' nor 'ice' member exist in JSON message [%s]\n", text);
free(g_offer);
free(g_answer);
free(g_remote_desc);
+ g_list_free_full(g_ice_candidates, free);
elm_exit();
}
} else if (strncmp(cmd, "un", 2) == 0) {
_webrtc_unset_negotiation_needed_cb();
+ } else if (strncmp(cmd, "si", 2) == 0) {
+ _webrtc_set_ice_candidate_cb();
+
+ } else if (strncmp(cmd, "ui", 2) == 0) {
+ _webrtc_unset_ice_candidate_cb();
+
} else if (strncmp(cmd, "co", 2) == 0) {
_webrtc_create_offer();
} else if (strncmp(cmd, "sd", 2) == 0) {
g_menu_state = CURRENT_STATUS_SEND_LOCAL_DESCRIPTION;
+ } else if (strncmp(cmd, "ac", 2) == 0) {
+ _webrtc_add_ice_candidate();
+
} else {
g_print("unknown menu \n");
}
g_print("us. Unset state changed callback\n");
g_print("sn. Set negotiation needed callback\t");
g_print("un. Unset negotiation needed callback\n");
+ g_print("si. Set ICE candidate callback\t");
+ g_print("ui. Unset ICE candidate callback\n");
g_print("co. Create offer\t");
g_print("ca. Create answer\n");
g_print("sl. Set local description\t");
g_print("sr. Set remote description\n");
+ g_print("ac. Add ICE candidate\n");
g_print("st. Set STUN server\n");
g_print("----------------------------------- App. Setting ----------------------------------------\n");
display_setting_status();