mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-13 12:51:16 +00:00
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:
parent
c8e79c9671
commit
5bf67feae8
2 changed files with 165 additions and 55 deletions
|
@ -47,12 +47,14 @@ static enum AppState app_state = 0;
|
||||||
static const gchar *peer_id = NULL;
|
static const gchar *peer_id = NULL;
|
||||||
static const gchar *server_url = "wss://webrtc.nirbheek.in:8443";
|
static const gchar *server_url = "wss://webrtc.nirbheek.in:8443";
|
||||||
static gboolean disable_ssl = FALSE;
|
static gboolean disable_ssl = FALSE;
|
||||||
|
static gboolean remote_is_offerer = FALSE;
|
||||||
|
|
||||||
static GOptionEntry entries[] =
|
static GOptionEntry entries[] =
|
||||||
{
|
{
|
||||||
{ "peer-id", 0, 0, G_OPTION_ARG_STRING, &peer_id, "String ID of the peer to connect to", "ID" },
|
{ "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" },
|
{ "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 },
|
{ "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 },
|
{ NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -213,21 +215,31 @@ send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
send_sdp_offer (GstWebRTCSessionDescription * offer)
|
send_sdp_to_peer (GstWebRTCSessionDescription *desc)
|
||||||
{
|
{
|
||||||
gchar *text;
|
gchar *text;
|
||||||
JsonObject *msg, *sdp;
|
JsonObject *msg, *sdp;
|
||||||
|
|
||||||
if (app_state < PEER_CALL_NEGOTIATING) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
text = gst_sdp_message_as_text (offer->sdp);
|
text = gst_sdp_message_as_text (desc->sdp);
|
||||||
g_print ("Sending offer:\n%s\n", text);
|
|
||||||
|
|
||||||
sdp = json_object_new ();
|
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);
|
json_object_set_string_member (sdp, "sdp", text);
|
||||||
g_free (text);
|
g_free (text);
|
||||||
|
|
||||||
|
@ -261,18 +273,24 @@ on_offer_created (GstPromise * promise, gpointer user_data)
|
||||||
gst_promise_unref (promise);
|
gst_promise_unref (promise);
|
||||||
|
|
||||||
/* Send offer to peer */
|
/* Send offer to peer */
|
||||||
send_sdp_offer (offer);
|
send_sdp_to_peer (offer);
|
||||||
gst_webrtc_session_description_free (offer);
|
gst_webrtc_session_description_free (offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_negotiation_needed (GstElement * element, gpointer user_data)
|
on_negotiation_needed (GstElement * element, gpointer user_data)
|
||||||
{
|
{
|
||||||
GstPromise *promise;
|
|
||||||
|
|
||||||
app_state = PEER_CALL_NEGOTIATING;
|
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 "
|
#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;
|
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
|
static gboolean
|
||||||
start_pipeline (void)
|
start_pipeline (void)
|
||||||
{
|
{
|
||||||
|
@ -359,6 +400,8 @@ start_pipeline (void)
|
||||||
* added by us too, see on_server_message() */
|
* added by us too, see on_server_message() */
|
||||||
g_signal_connect (webrtc1, "on-ice-candidate",
|
g_signal_connect (webrtc1, "on-ice-candidate",
|
||||||
G_CALLBACK (send_ice_candidate_message), NULL);
|
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);
|
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);
|
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 */
|
/* One mega message handler for our asynchronous calling mechanism */
|
||||||
static void
|
static void
|
||||||
on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
|
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");
|
sdptype = json_object_get_string_member (child, "type");
|
||||||
/* In this example, we always create the offer and receive one answer.
|
/* In this example, we create the offer and receive one answer by default,
|
||||||
* See tests/examples/webrtcbidirectional.c in gst-plugins-bad for how to
|
* but it's possible to comment out the offer creation and wait for an offer
|
||||||
* handle offers from peers and reply with answers using webrtcbin. */
|
* instead, so we handle either here.
|
||||||
g_assert_cmpstr (sdptype, ==, "answer");
|
*
|
||||||
|
* 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");
|
text = json_object_get_string_member (child, "sdp");
|
||||||
|
|
||||||
g_print ("Received answer:\n%s\n", text);
|
|
||||||
|
|
||||||
ret = gst_sdp_message_new (&sdp);
|
ret = gst_sdp_message_new (&sdp);
|
||||||
g_assert_cmphex (ret, ==, GST_SDP_OK);
|
g_assert_cmphex (ret, ==, GST_SDP_OK);
|
||||||
|
|
||||||
ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp);
|
ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp);
|
||||||
g_assert_cmphex (ret, ==, GST_SDP_OK);
|
g_assert_cmphex (ret, ==, GST_SDP_OK);
|
||||||
|
|
||||||
answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
|
if (g_str_equal (sdptype, "answer")) {
|
||||||
sdp);
|
g_print ("Received answer:\n%s\n", text);
|
||||||
g_assert_nonnull (answer);
|
answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
|
||||||
|
sdp);
|
||||||
/* Set remote description on our pipeline */
|
g_assert_nonnull (answer);
|
||||||
{
|
|
||||||
GstPromise *promise = gst_promise_new ();
|
/* Set remote description on our pipeline */
|
||||||
g_signal_emit_by_name (webrtc1, "set-remote-description", answer,
|
{
|
||||||
promise);
|
GstPromise *promise = gst_promise_new ();
|
||||||
gst_promise_interrupt (promise);
|
g_signal_emit_by_name (webrtc1, "set-remote-description", answer,
|
||||||
gst_promise_unref (promise);
|
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")) {
|
} else if (json_object_has_member (object, "ice")) {
|
||||||
const gchar *candidate;
|
const gchar *candidate;
|
||||||
gint sdpmlineindex;
|
gint sdpmlineindex;
|
||||||
|
|
|
@ -93,12 +93,16 @@ function onIncomingSDP(sdp) {
|
||||||
function onLocalDescription(desc) {
|
function onLocalDescription(desc) {
|
||||||
console.log("Got local description: " + JSON.stringify(desc));
|
console.log("Got local description: " + JSON.stringify(desc));
|
||||||
peer_connection.setLocalDescription(desc).then(function() {
|
peer_connection.setLocalDescription(desc).then(function() {
|
||||||
setStatus("Sending SDP answer");
|
setStatus("Sending SDP " + desc.type);
|
||||||
sdp = {'sdp': peer_connection.localDescription}
|
sdp = {'sdp': peer_connection.localDescription}
|
||||||
ws_conn.send(JSON.stringify(sdp));
|
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
|
// ICE candidate received from peer, add it to the peer connection
|
||||||
function onIncomingICE(ice) {
|
function onIncomingICE(ice) {
|
||||||
var candidate = new RTCIceCandidate(ice);
|
var candidate = new RTCIceCandidate(ice);
|
||||||
|
@ -116,29 +120,36 @@ function onServerMessage(event) {
|
||||||
handleIncomingError(event.data);
|
handleIncomingError(event.data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Handle incoming JSON SDP and ICE messages
|
if (event.data.startsWith("OFFER_REQUEST")) {
|
||||||
try {
|
// The peer wants us to set up and then send an offer
|
||||||
msg = JSON.parse(event.data);
|
if (!peer_connection)
|
||||||
} catch (e) {
|
createCall(null).then (generateOffer);
|
||||||
if (e instanceof SyntaxError) {
|
}
|
||||||
handleIncomingError("Error parsing incoming JSON: " + event.data);
|
else {
|
||||||
} else {
|
// Handle incoming JSON SDP and ICE messages
|
||||||
handleIncomingError("Unknown error parsing response: " + event.data);
|
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
|
// Incoming JSON signals the beginning of a call
|
||||||
if (!peer_connection)
|
if (!peer_connection)
|
||||||
createCall(msg);
|
createCall(msg);
|
||||||
|
|
||||||
if (msg.sdp != null) {
|
if (msg.sdp != null) {
|
||||||
onIncomingSDP(msg.sdp);
|
onIncomingSDP(msg.sdp);
|
||||||
} else if (msg.ice != null) {
|
} else if (msg.ice != null) {
|
||||||
onIncomingICE(msg.ice);
|
onIncomingICE(msg.ice);
|
||||||
} else {
|
} else {
|
||||||
handleIncomingError("Unknown incoming JSON: " + msg);
|
handleIncomingError("Unknown incoming JSON: " + msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +297,7 @@ function createCall(msg) {
|
||||||
return stream;
|
return stream;
|
||||||
}).catch(setError);
|
}).catch(setError);
|
||||||
|
|
||||||
if (!msg.sdp) {
|
if (msg != null && !msg.sdp) {
|
||||||
console.log("WARNING: First message wasn't an SDP message!?");
|
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}));
|
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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue