sendrecv: Add a switch for remote-offerer

Add a switch to the command line utility that makes it request
the initial offer from the peer instead of generating it.

Modify the webrtc.js example to support a new REQUEST_OFFER
message, and generate the offer when receiving it.
This commit is contained in:
Jan Schmidt 2020-03-05 03:03:17 +11:00
parent c8e79c9671
commit 5bf67feae8
2 changed files with 165 additions and 55 deletions

View file

@ -47,12 +47,14 @@ static enum AppState app_state = 0;
static const gchar *peer_id = NULL;
static const gchar *server_url = "wss://webrtc.nirbheek.in:8443";
static gboolean disable_ssl = FALSE;
static gboolean remote_is_offerer = FALSE;
static GOptionEntry entries[] =
{
{ "peer-id", 0, 0, G_OPTION_ARG_STRING, &peer_id, "String ID of the peer to connect to", "ID" },
{ "server", 0, 0, G_OPTION_ARG_STRING, &server_url, "Signalling server to connect to", "URL" },
{ "disable-ssl", 0, 0, G_OPTION_ARG_NONE, &disable_ssl, "Disable ssl", NULL },
{ "remote-offerer", 0, 0, G_OPTION_ARG_NONE, &remote_is_offerer, "Request that the peer generate the offer and we'll answer", NULL },
{ NULL },
};
@ -213,21 +215,31 @@ send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex,
}
static void
send_sdp_offer (GstWebRTCSessionDescription * offer)
send_sdp_to_peer (GstWebRTCSessionDescription *desc)
{
gchar *text;
JsonObject *msg, *sdp;
if (app_state < PEER_CALL_NEGOTIATING) {
cleanup_and_quit_loop ("Can't send offer, not in call", APP_STATE_ERROR);
cleanup_and_quit_loop ("Can't send SDP to peer, not in call", APP_STATE_ERROR);
return;
}
text = gst_sdp_message_as_text (offer->sdp);
g_print ("Sending offer:\n%s\n", text);
text = gst_sdp_message_as_text (desc->sdp);
sdp = json_object_new ();
json_object_set_string_member (sdp, "type", "offer");
if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) {
g_print ("Sending offer:\n%s\n", text);
json_object_set_string_member (sdp, "type", "offer");
}
else if (desc->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
g_print ("Sending answer:\n%s\n", text);
json_object_set_string_member (sdp, "type", "answer");
}
else {
g_assert_not_reached ();
}
json_object_set_string_member (sdp, "sdp", text);
g_free (text);
@ -261,18 +273,24 @@ on_offer_created (GstPromise * promise, gpointer user_data)
gst_promise_unref (promise);
/* Send offer to peer */
send_sdp_offer (offer);
send_sdp_to_peer (offer);
gst_webrtc_session_description_free (offer);
}
static void
on_negotiation_needed (GstElement * element, gpointer user_data)
{
GstPromise *promise;
app_state = PEER_CALL_NEGOTIATING;
promise = gst_promise_new_with_change_func (on_offer_created, user_data, NULL);;
g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
if (remote_is_offerer) {
gchar *msg = g_strdup_printf ("OFFER_REQUEST");
soup_websocket_connection_send_text (ws_conn, msg);
g_free (msg);
} else {
GstPromise *promise;
promise = gst_promise_new_with_change_func (on_offer_created, user_data, NULL);;
g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
}
}
#define STUN_SERVER " stun-server=stun://stun.l.google.com:19302 "
@ -327,6 +345,29 @@ on_data_channel (GstElement * webrtc, GObject * data_channel, gpointer user_data
receive_channel = data_channel;
}
static void
on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec,
gpointer user_data)
{
GstWebRTCICEGatheringState ice_gather_state;
const gchar *new_state = "unknown";
g_object_get (webrtcbin, "ice-gathering-state", &ice_gather_state,
NULL);
switch (ice_gather_state) {
case GST_WEBRTC_ICE_GATHERING_STATE_NEW:
new_state = "new";
break;
case GST_WEBRTC_ICE_GATHERING_STATE_GATHERING:
new_state = "gathering";
break;
case GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE:
new_state = "complete";
break;
}
g_print ("ICE gathering state changed to %s\n", new_state);
}
static gboolean
start_pipeline (void)
{
@ -359,6 +400,8 @@ start_pipeline (void)
* added by us too, see on_server_message() */
g_signal_connect (webrtc1, "on-ice-candidate",
G_CALLBACK (send_ice_candidate_message), NULL);
g_signal_connect (webrtc1, "notify::ice-gathering-state",
G_CALLBACK (on_ice_gathering_state_notify), NULL);
gst_element_set_state (pipe1, GST_STATE_READY);
@ -445,6 +488,55 @@ on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED,
cleanup_and_quit_loop ("Server connection closed", 0);
}
/* Answer created by our pipeline, to be sent to the peer */
static void
on_answer_created (GstPromise * promise, gpointer user_data)
{
GstWebRTCSessionDescription *answer = NULL;
const GstStructure *reply;
g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING);
g_assert_cmphex (gst_promise_wait(promise), ==, GST_PROMISE_RESULT_REPLIED);
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "answer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
gst_promise_unref (promise);
promise = gst_promise_new ();
g_signal_emit_by_name (webrtc1, "set-local-description", answer, promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
/* Send answer to peer */
send_sdp_to_peer (answer);
gst_webrtc_session_description_free (answer);
}
static void
on_offer_received (GstSDPMessage *sdp)
{
GstWebRTCSessionDescription *offer = NULL;
GstPromise *promise;
offer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_OFFER, sdp);
g_assert_nonnull (offer);
/* Set remote description on our pipeline */
{
promise = gst_promise_new ();
g_signal_emit_by_name (webrtc1, "set-remote-description", offer,
promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
}
gst_webrtc_session_description_free (offer);
promise = gst_promise_new_with_change_func (on_answer_created, NULL,
NULL);
g_signal_emit_by_name (webrtc1, "create-answer", NULL, promise);
}
/* One mega message handler for our asynchronous calling mechanism */
static void
on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
@ -550,35 +642,39 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
}
sdptype = json_object_get_string_member (child, "type");
/* In this example, we always create the offer and receive one answer.
* See tests/examples/webrtcbidirectional.c in gst-plugins-bad for how to
* handle offers from peers and reply with answers using webrtcbin. */
g_assert_cmpstr (sdptype, ==, "answer");
/* In this example, we create the offer and receive one answer by default,
* but it's possible to comment out the offer creation and wait for an offer
* instead, so we handle either here.
*
* See tests/examples/webrtcbidirectional.c in gst-plugins-bad for another
* example how to handle offers from peers and reply with answers using webrtcbin. */
text = json_object_get_string_member (child, "sdp");
g_print ("Received answer:\n%s\n", text);
ret = gst_sdp_message_new (&sdp);
g_assert_cmphex (ret, ==, GST_SDP_OK);
ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp);
g_assert_cmphex (ret, ==, GST_SDP_OK);
answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
sdp);
g_assert_nonnull (answer);
/* Set remote description on our pipeline */
{
GstPromise *promise = gst_promise_new ();
g_signal_emit_by_name (webrtc1, "set-remote-description", answer,
promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
if (g_str_equal (sdptype, "answer")) {
g_print ("Received answer:\n%s\n", text);
answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
sdp);
g_assert_nonnull (answer);
/* Set remote description on our pipeline */
{
GstPromise *promise = gst_promise_new ();
g_signal_emit_by_name (webrtc1, "set-remote-description", answer,
promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
}
app_state = PEER_CALL_STARTED;
}
else {
g_print ("Received offer:\n%s\n", text);
on_offer_received (sdp);
}
app_state = PEER_CALL_STARTED;
} else if (json_object_has_member (object, "ice")) {
const gchar *candidate;
gint sdpmlineindex;

View file

@ -93,12 +93,16 @@ function onIncomingSDP(sdp) {
function onLocalDescription(desc) {
console.log("Got local description: " + JSON.stringify(desc));
peer_connection.setLocalDescription(desc).then(function() {
setStatus("Sending SDP answer");
setStatus("Sending SDP " + desc.type);
sdp = {'sdp': peer_connection.localDescription}
ws_conn.send(JSON.stringify(sdp));
});
}
function generateOffer() {
peer_connection.createOffer().then(onLocalDescription).catch(setError);
}
// ICE candidate received from peer, add it to the peer connection
function onIncomingICE(ice) {
var candidate = new RTCIceCandidate(ice);
@ -116,29 +120,36 @@ function onServerMessage(event) {
handleIncomingError(event.data);
return;
}
// Handle incoming JSON SDP and ICE messages
try {
msg = JSON.parse(event.data);
} catch (e) {
if (e instanceof SyntaxError) {
handleIncomingError("Error parsing incoming JSON: " + event.data);
} else {
handleIncomingError("Unknown error parsing response: " + event.data);
if (event.data.startsWith("OFFER_REQUEST")) {
// The peer wants us to set up and then send an offer
if (!peer_connection)
createCall(null).then (generateOffer);
}
else {
// Handle incoming JSON SDP and ICE messages
try {
msg = JSON.parse(event.data);
} catch (e) {
if (e instanceof SyntaxError) {
handleIncomingError("Error parsing incoming JSON: " + event.data);
} else {
handleIncomingError("Unknown error parsing response: " + event.data);
}
return;
}
return;
}
// Incoming JSON signals the beginning of a call
if (!peer_connection)
createCall(msg);
// Incoming JSON signals the beginning of a call
if (!peer_connection)
createCall(msg);
if (msg.sdp != null) {
onIncomingSDP(msg.sdp);
} else if (msg.ice != null) {
onIncomingICE(msg.ice);
} else {
handleIncomingError("Unknown incoming JSON: " + msg);
}
if (msg.sdp != null) {
onIncomingSDP(msg.sdp);
} else if (msg.ice != null) {
onIncomingICE(msg.ice);
} else {
handleIncomingError("Unknown incoming JSON: " + msg);
}
}
}
}
@ -286,7 +297,7 @@ function createCall(msg) {
return stream;
}).catch(setError);
if (!msg.sdp) {
if (msg != null && !msg.sdp) {
console.log("WARNING: First message wasn't an SDP message!?");
}
@ -300,5 +311,8 @@ function createCall(msg) {
ws_conn.send(JSON.stringify({'ice': event.candidate}));
};
setStatus("Created peer connection for call, waiting for SDP");
if (msg != null)
setStatus("Created peer connection for call, waiting for SDP");
return local_stream_promise;
}