From d6f4e60bf5df325090b6132cc27fc7251371bfdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 10:42:34 +0200 Subject: [PATCH 001/412] network/http-launch: Initial commit --- network/http-launch/http-launch.c | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 network/http-launch/http-launch.c diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c new file mode 100644 index 0000000000..3b44ad2d80 --- /dev/null +++ b/network/http-launch/http-launch.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int +main (gint argc, gchar **argv) +{ + + return 0; +} From d72c7a85baf1d26e217a86d971f68e1591b0d422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 12:58:43 +0200 Subject: [PATCH 002/412] network/http-launch: Add initial, working version of the code --- network/http-launch/http-launch.c | 402 +++++++++++++++++++++++++++++- 1 file changed, 400 insertions(+), 2 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 3b44ad2d80..064d506c90 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -1,10 +1,408 @@ +/* + * Copyright (C) 2009 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include -int -main (gint argc, gchar **argv) +typedef struct { + gchar *name; + GSocketConnection *connection; + GSocket *socket; + GInputStream *istream; + GOutputStream *ostream; + GSource *isource; + GByteArray *current_message; +} Client; + +static GMainLoop *loop = NULL; +G_LOCK_DEFINE_STATIC (clients); +static GList *clients = NULL; +static GstElement *pipeline = NULL; +static GstElement *multisocketsink = NULL; +static gboolean started = FALSE; + +static void +remove_client (Client * client) +{ + g_print ("Removing connection %s\n", client->name); + + g_free (client->name); + + if (client->isource) { + g_source_destroy (client->isource); + g_source_unref (client->isource); + } + g_object_unref (client->connection); + g_byte_array_unref (client->current_message); + + G_LOCK (clients); + clients = g_list_remove (clients, client); + G_UNLOCK (clients); + + g_slice_free (Client, client); +} + +static void +write_bytes (Client * client, const gchar * data, guint len) +{ + gssize w; + GError *err = NULL; + + /* TODO: We assume this never blocks */ + do { + w = g_output_stream_write (client->ostream, data, len, NULL, &err); + if (w > 0) + len -= w; + } while (w >= 0 && len > 0); + + if (w < 0) { + remove_client (client); + } +} + +static void +client_message (Client * client, const gchar * data, guint len) +{ + gchar **lines = g_strsplit_set (data, "\r\n", -1); + + if (g_str_has_prefix (lines[0], "HEAD")) { + gchar **parts = g_strsplit (lines[0], " ", -1); + gchar *response; + const gchar *http_version; + + /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ + if (*parts[2] != '\0') + http_version = parts[2]; + else + http_version = "HTTP/1.1"; + + if (strcmp (parts[1], "/") == 0) { + response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); + } else { + response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); + } + write_bytes (client, response, strlen (response)); + g_free (response); + g_strfreev (parts); + } else if (g_str_has_prefix (lines[0], "GET")) { + gchar **parts = g_strsplit (lines[0], " ", -1); + gchar *response; + const gchar *http_version; + gboolean ok = FALSE; + + /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ + if (*parts[2] != '\0') + http_version = parts[2]; + else + http_version = "HTTP/1.1"; + + if (strcmp (parts[1], "/") == 0) { + response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); + ok = TRUE; + } else { + response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); + } + write_bytes (client, response, strlen (response)); + g_free (response); + g_strfreev (parts); + + if (ok) { + g_source_destroy (client->isource); + g_source_unref (client->isource); + client->isource = NULL; + g_print ("Starting to stream to %s\n", client->name); + g_signal_emit_by_name (multisocketsink, "add", client->socket); + + if (!started) { + g_print ("Starting pipeline\n"); + if (gst_element_set_state (pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + g_print ("Failed to start pipeline\n"); + g_main_loop_quit (loop); + } + started = TRUE; + } + } + } else { + gchar **parts = g_strsplit (lines[0], " ", -1); + gchar *response; + const gchar *http_version; + + /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ + if (*parts[2] != '\0') + http_version = parts[2]; + else + http_version = "HTTP/1.1"; + + response = g_strdup_printf ("%s 400 Bad Request\r\n\r\n", http_version); + write_bytes (client, response, strlen (response)); + g_free (response); + g_strfreev (parts); + remove_client (client); + } + + g_strfreev (lines); +} + +static gboolean +on_read_bytes (GPollableInputStream * stream, Client * client) +{ + gssize r; + gchar data[4096]; + GError *err = NULL; + + do { + r = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM + (client->istream), data, sizeof (data), NULL, &err); + if (r > 0) + g_byte_array_append (client->current_message, (guint8 *) data, r); + } while (r > 0); + + if (r == 0 || g_error_matches (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + guint8 *tmp = client->current_message->data; + + /* Read everything */ + g_clear_error (&err); + + while (client->current_message->len > 3) { + if (tmp[0] == 0x0d && tmp[1] == 0x0a && tmp[2] == 0x0d && tmp[3] == 0x0a) { + guint len = tmp - client->current_message->data + 4; + + client_message (client, (gchar *) client->current_message->data, len); + g_byte_array_remove_range (client->current_message, 0, len); + tmp = client->current_message->data; + } else { + tmp++; + } + } + + if (r == 0) { + remove_client (client); + return FALSE; + } + + return TRUE; + } else { + g_assert_not_reached (); + } + + return FALSE; +} + +static gboolean +on_new_connection (GSocketService * service, GSocketConnection * connection, + GObject * source_object, gpointer user_data) +{ + Client *client = g_slice_new0 (Client); + GSocketAddress *addr; + GInetAddress *iaddr; + gchar *ip; + guint16 port; + + addr = g_socket_connection_get_remote_address (connection, NULL); + iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)); + port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr)); + ip = g_inet_address_to_string (iaddr); + client->name = g_strdup_printf ("%s:%u", ip, port); + g_free (ip); + g_object_unref (addr); + + g_print ("New connection %s\n", client->name); + + client->connection = g_object_ref (connection); + client->socket = g_socket_connection_get_socket (connection); + client->istream = + g_io_stream_get_input_stream (G_IO_STREAM (client->connection)); + client->ostream = + g_io_stream_get_output_stream (G_IO_STREAM (client->connection)); + client->current_message = g_byte_array_sized_new (1024); + + client->isource = + g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM + (client->istream), NULL); + g_source_set_callback (client->isource, (GSourceFunc) on_read_bytes, client, + NULL); + g_source_attach (client->isource, NULL); + + G_LOCK (clients); + clients = g_list_prepend (clients, client); + G_UNLOCK (clients); + + return TRUE; +} + +static gboolean +on_message (GstBus * bus, GstMessage * message, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR:{ + gchar *debug; + GError *err; + + gst_message_parse_error (message, &err, &debug); + g_print ("Error %s\n", err->message); + g_error_free (err); + g_free (debug); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_WARNING:{ + gchar *debug; + GError *err; + + gst_message_parse_error (message, &err, &debug); + g_print ("Warning %s\n", err->message); + g_error_free (err); + g_free (debug); + } + default: + break; + } + + return TRUE; +} + +static void +on_client_socket_removed (GstElement * element, GSocket * socket, + gpointer user_data) +{ + GList *l; + Client *client = NULL; + + G_LOCK (clients); + for (l = clients; l; l = l->next) { + Client *tmp = l->data; + if (socket == tmp->socket) { + client = tmp; + break; + } + } + G_UNLOCK (clients); + + if (client) + remove_client (client); +} + +int +main (gint argc, gchar ** argv) +{ + GSocketService *service; + GstElement *bin, *stream; + GstPad *srcpad, *ghostpad, *sinkpad; + GError *err = NULL; + GstBus *bus; + + gst_init (&argc, &argv); + + if (argc < 2) { + g_print ("usage: %s \n" + "example: %s \"( videotestsrc ! theoraenc ! oggmux name=stream )\"\n", + argv[0], argv[0]); + return -1; + } + + bin = gst_parse_launchv ((const gchar **) argv + 1, &err); + if (!bin) { + g_print ("invalid pipeline: %s\n", err->message); + g_clear_error (&err); + return -2; + } + + stream = gst_bin_get_by_name (GST_BIN (bin), "stream"); + if (!stream) { + g_print ("no element with name \"stream\" found\n"); + gst_object_unref (bin); + return -3; + } + + srcpad = gst_element_get_static_pad (stream, "src"); + if (!srcpad) { + g_print ("no \"src\" pad in element \"stream\" found\n"); + gst_object_unref (stream); + gst_object_unref (bin); + return -4; + } + + ghostpad = gst_ghost_pad_new ("src", srcpad); + gst_element_add_pad (GST_ELEMENT (bin), ghostpad); + gst_object_unref (srcpad); + + pipeline = gst_pipeline_new (NULL); + + multisocketsink = gst_element_factory_make ("multisocketsink", NULL); + g_object_set (multisocketsink, + "unit-format", GST_FORMAT_TIME, + "units-max", (gint64) 7 * GST_SECOND, + "units-soft-max", (gint64) 3 * GST_SECOND, + "recover-policy", 3 /* keyframe */ , + "timeout", (guint64) 7 * GST_SECOND, + "sync-method", 1 /* next-keyframe */ , + NULL); + + gst_bin_add_many (GST_BIN (pipeline), bin, multisocketsink, NULL); + + sinkpad = gst_element_get_static_pad (multisocketsink, "sink"); + gst_pad_link (ghostpad, sinkpad); + gst_object_unref (sinkpad); + + bus = gst_element_get_bus (pipeline); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (on_message), NULL); + gst_object_unref (bus); + + g_signal_connect (multisocketsink, "client-socket-removed", + G_CALLBACK (on_client_socket_removed), NULL); + + loop = g_main_loop_new (NULL, FALSE); + + if (gst_element_set_state (pipeline, + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) { + gst_object_unref (pipeline); + g_main_loop_unref (loop); + g_print ("Failed to set pipeline to ready\n"); + return -5; + } + + service = g_socket_service_new (); + g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), 8080, NULL, + NULL); + + g_signal_connect (service, "incoming", G_CALLBACK (on_new_connection), NULL); + + g_socket_service_start (service); + + g_print ("Listening on http://127.0.0.1:8080/\n"); + + g_main_loop_run (loop); + + g_socket_service_stop (service); + g_object_unref (service); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + g_main_loop_unref (loop); return 0; } From 7c1b72ef488e98df2f68ab78a3f77e7a4bc65842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 13:03:51 +0200 Subject: [PATCH 003/412] network/http-launch: Fix example commandline --- network/http-launch/http-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 064d506c90..9b49003512 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -317,7 +317,7 @@ main (gint argc, gchar ** argv) if (argc < 2) { g_print ("usage: %s \n" - "example: %s \"( videotestsrc ! theoraenc ! oggmux name=stream )\"\n", + "example: %s ( videotestsrc ! theoraenc ! oggmux name=stream )\n", argv[0], argv[0]); return -1; } From 1cbacffb52da51d9e7f7e62596952621b02474db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 13:27:20 +0200 Subject: [PATCH 004/412] network/http-launch: Adjust timeout a bit --- network/http-launch/http-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 9b49003512..07fbbca898 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -356,7 +356,7 @@ main (gint argc, gchar ** argv) "units-max", (gint64) 7 * GST_SECOND, "units-soft-max", (gint64) 3 * GST_SECOND, "recover-policy", 3 /* keyframe */ , - "timeout", (guint64) 7 * GST_SECOND, + "timeout", (guint64) 10 * GST_SECOND, "sync-method", 1 /* next-keyframe */ , NULL); From f132da65a7b5eeee15afa6824aa8a5c73a14eb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 13:54:58 +0200 Subject: [PATCH 005/412] network/http-launch: Add EOS handling Just quit on EOS for now --- network/http-launch/http-launch.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 07fbbca898..fd104fd668 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -275,6 +275,11 @@ on_message (GstBus * bus, GstMessage * message, gpointer user_data) g_print ("Warning %s\n", err->message); g_error_free (err); g_free (debug); + break; + } + case GST_MESSAGE_EOS:{ + g_print ("EOS\n"); + g_main_loop_quit (loop); } default: break; From 1445b2bf9205c3d99ec55e1ce61f678e1ee7c7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:24:53 +0200 Subject: [PATCH 006/412] network/http-launch: Fix copyright year --- network/http-launch/http-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index fd104fd668..7c6c8a4684 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Sebastian Dröge + * Copyright (C) 2013 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public From c8cfba9acc09c61197c637707e8aa28754e3f0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:32:16 +0200 Subject: [PATCH 007/412] network/http-launch: Improve error reporting a bit and add some FIXME/TODO comments here and there --- network/http-launch/http-launch.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 7c6c8a4684..4bf9cffcfc 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -74,9 +74,13 @@ write_bytes (Client * client, const gchar * data, guint len) w = g_output_stream_write (client->ostream, data, len, NULL, &err); if (w > 0) len -= w; - } while (w >= 0 && len > 0); + } while (w > 0 && len > 0); - if (w < 0) { + if (w <= 0) { + if (err) { + g_print ("Write error %s\n", err->message); + g_clear_error (&err); + } remove_client (client); } } @@ -86,6 +90,7 @@ client_message (Client * client, const gchar * data, guint len) { gchar **lines = g_strsplit_set (data, "\r\n", -1); + /* TODO: Make HTTP handling more robust, add proper HTTP 1.0 support */ if (g_str_has_prefix (lines[0], "HEAD")) { gchar **parts = g_strsplit (lines[0], " ", -1); gchar *response; @@ -179,10 +184,12 @@ on_read_bytes (GPollableInputStream * stream, Client * client) g_byte_array_append (client->current_message, (guint8 *) data, r); } while (r > 0); - if (r == 0 || g_error_matches (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + if (r == 0) { + remove_client (client); + return FALSE; + } else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { guint8 *tmp = client->current_message->data; - /* Read everything */ g_clear_error (&err); while (client->current_message->len > 3) { @@ -197,14 +204,14 @@ on_read_bytes (GPollableInputStream * stream, Client * client) } } - if (r == 0) { - remove_client (client); - return FALSE; - } + /* FIXME: If too large, disconnect client */ return TRUE; } else { - g_assert_not_reached (); + g_print ("Read error %s\n", err->message); + g_clear_error (&err); + remove_client (client); + return FALSE; } return FALSE; @@ -230,6 +237,7 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_print ("New connection %s\n", client->name); + /* TODO: Need timeout */ client->connection = g_object_ref (connection); client->socket = g_socket_connection_get_socket (connection); client->istream = From 9228d05bf340ec70951c20b626b885162608d9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:35:20 +0200 Subject: [PATCH 008/412] network/http-launch: Timeout if client did not do a GET after 5 seconds --- network/http-launch/http-launch.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 4bf9cffcfc..238385ff7b 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -31,7 +31,7 @@ typedef struct GSocket *socket; GInputStream *istream; GOutputStream *ostream; - GSource *isource; + GSource *isource, *tosource; GByteArray *current_message; } Client; @@ -53,6 +53,10 @@ remove_client (Client * client) g_source_destroy (client->isource); g_source_unref (client->isource); } + if (client->tosource) { + g_source_destroy (client->tosource); + g_source_unref (client->tosource); + } g_object_unref (client->connection); g_byte_array_unref (client->current_message); @@ -136,6 +140,9 @@ client_message (Client * client, const gchar * data, guint len) g_source_destroy (client->isource); g_source_unref (client->isource); client->isource = NULL; + g_source_destroy (client->tosource); + g_source_unref (client->tosource); + client->tosource = NULL; g_print ("Starting to stream to %s\n", client->name); g_signal_emit_by_name (multisocketsink, "add", client->socket); @@ -170,6 +177,15 @@ client_message (Client * client, const gchar * data, guint len) g_strfreev (lines); } +static gboolean +on_timeout (Client * client) +{ + g_print ("Timeout\n"); + remove_client (client); + + return FALSE; +} + static gboolean on_read_bytes (GPollableInputStream * stream, Client * client) { @@ -237,7 +253,6 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_print ("New connection %s\n", client->name); - /* TODO: Need timeout */ client->connection = g_object_ref (connection); client->socket = g_socket_connection_get_socket (connection); client->istream = @@ -246,6 +261,11 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_io_stream_get_output_stream (G_IO_STREAM (client->connection)); client->current_message = g_byte_array_sized_new (1024); + client->tosource = g_timeout_source_new_seconds (5); + g_source_set_callback (client->isource, (GSourceFunc) on_timeout, client, + NULL); + g_source_attach (client->tosource, NULL); + client->isource = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (client->istream), NULL); From e38e9ce609c5aae7404b71f14cc76492c3f2c2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:38:01 +0200 Subject: [PATCH 009/412] network/http-launch: Disconnect clients if there was no valid HTTP request after 1MB of data --- network/http-launch/http-launch.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 238385ff7b..7b0289056d 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -220,7 +220,11 @@ on_read_bytes (GPollableInputStream * stream, Client * client) } } - /* FIXME: If too large, disconnect client */ + if (client->current_message->len >= 1024 * 1024) { + g_print ("No complete request after 1MB of data\n"); + remove_client (client); + return FALSE; + } return TRUE; } else { From 466d2496479a4391952866e4cada00be80d7e426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:39:07 +0200 Subject: [PATCH 010/412] network/http-launch: Fix writing response if the write could not be finished in one rurun --- network/http-launch/http-launch.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 7b0289056d..13341d9a35 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -76,8 +76,10 @@ write_bytes (Client * client, const gchar * data, guint len) /* TODO: We assume this never blocks */ do { w = g_output_stream_write (client->ostream, data, len, NULL, &err); - if (w > 0) + if (w > 0) { len -= w; + data += w; + } } while (w > 0 && len > 0); if (w <= 0) { From c8a308eee2664b6c0e6ec3341f6b2a3590155db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 18:53:51 +0200 Subject: [PATCH 011/412] network/http-launch: Make HTTP handling a bit more robust --- network/http-launch/http-launch.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 13341d9a35..5d741e2e54 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -96,19 +96,17 @@ client_message (Client * client, const gchar * data, guint len) { gchar **lines = g_strsplit_set (data, "\r\n", -1); - /* TODO: Make HTTP handling more robust, add proper HTTP 1.0 support */ if (g_str_has_prefix (lines[0], "HEAD")) { gchar **parts = g_strsplit (lines[0], " ", -1); gchar *response; const gchar *http_version; - /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ - if (*parts[2] != '\0') + if (parts[1] && parts[2] && *parts[2] != '\0') http_version = parts[2]; else - http_version = "HTTP/1.1"; + http_version = "HTTP/1.0"; - if (strcmp (parts[1], "/") == 0) { + if (parts[1] && strcmp (parts[1], "/") == 0) { response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); } else { response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); @@ -122,13 +120,12 @@ client_message (Client * client, const gchar * data, guint len) const gchar *http_version; gboolean ok = FALSE; - /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ - if (*parts[2] != '\0') + if (parts[1] && parts[2] && *parts[2] != '\0') http_version = parts[2]; else - http_version = "HTTP/1.1"; + http_version = "HTTP/1.0"; - if (strcmp (parts[1], "/") == 0) { + if (parts[1] && strcmp (parts[1], "/") == 0) { response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); ok = TRUE; } else { @@ -163,11 +160,10 @@ client_message (Client * client, const gchar * data, guint len) gchar *response; const gchar *http_version; - /* FIXME: Assume that 3 parts at least, probably wrong for HTTP 1.0 */ - if (*parts[2] != '\0') + if (parts[1] && parts[2] && *parts[2] != '\0') http_version = parts[2]; else - http_version = "HTTP/1.1"; + http_version = "HTTP/1.0"; response = g_strdup_printf ("%s 400 Bad Request\r\n\r\n", http_version); write_bytes (client, response, strlen (response)); @@ -268,7 +264,7 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, client->current_message = g_byte_array_sized_new (1024); client->tosource = g_timeout_source_new_seconds (5); - g_source_set_callback (client->isource, (GSourceFunc) on_timeout, client, + g_source_set_callback (client->tosource, (GSourceFunc) on_timeout, client, NULL); g_source_attach (client->tosource, NULL); From dcf3da3d5d98771b45c871965e07c9045d0dbdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 28 Jul 2013 19:01:34 +0200 Subject: [PATCH 012/412] network/http-launch: Append \0 at the end of the requests to make the string functions happy --- network/http-launch/http-launch.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 5d741e2e54..4ab7a275b5 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -208,8 +208,10 @@ on_read_bytes (GPollableInputStream * stream, Client * client) while (client->current_message->len > 3) { if (tmp[0] == 0x0d && tmp[1] == 0x0a && tmp[2] == 0x0d && tmp[3] == 0x0a) { - guint len = tmp - client->current_message->data + 4; + guint len; + g_byte_array_append (client->current_message, (const guint8 *) "\0", 1); + len = tmp - client->current_message->data + 5; client_message (client, (gchar *) client->current_message->data, len); g_byte_array_remove_range (client->current_message, 0, len); tmp = client->current_message->data; From 5c289ec20a7b9f5e1e2a692881c46ec98dad6cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 27 Jul 2014 17:58:35 +0200 Subject: [PATCH 013/412] playback/player: Port gst-play from gst-plugins-base to GstPlayer --- playback/player/gst-play/gst-play-kb.c | 143 +++++++ playback/player/gst-play/gst-play-kb.h | 35 ++ playback/player/gst-play/gst-play.c | 514 +++++++++++++++++++++++++ 3 files changed, 692 insertions(+) create mode 100644 playback/player/gst-play/gst-play-kb.c create mode 100644 playback/player/gst-play/gst-play-kb.h create mode 100644 playback/player/gst-play/gst-play.c diff --git a/playback/player/gst-play/gst-play-kb.c b/playback/player/gst-play/gst-play-kb.c new file mode 100644 index 0000000000..791269d957 --- /dev/null +++ b/playback/player/gst-play/gst-play-kb.c @@ -0,0 +1,143 @@ +/* GStreamer command line playback testing utility - keyboard handling helpers + * + * Copyright (C) 2013 Tim-Philipp Müller + * Copyright (C) 2013 Centricular Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst-play-kb.h" + +#include +#include +#include + +#ifdef G_OS_UNIX +#include +#include +#endif + +#include + +/* This is all not thread-safe, but doesn't have to be really */ + +#ifdef G_OS_UNIX + +static struct termios term_settings; +static gboolean term_settings_saved = FALSE; +static GstPlayKbFunc kb_callback; +static gpointer kb_callback_data; +static gulong io_watch_id; + +static gboolean +gst_play_kb_io_cb (GIOChannel * ioc, GIOCondition cond, gpointer user_data) +{ + GIOStatus status; + + if (cond & G_IO_IN) { + gchar buf[16] = { 0, }; + gsize read; + + status = g_io_channel_read_chars (ioc, buf, sizeof (buf) - 1, &read, NULL); + if (status == G_IO_STATUS_ERROR) + return FALSE; + if (status == G_IO_STATUS_NORMAL) { + if (kb_callback) + kb_callback (buf, kb_callback_data); + } + } + + return TRUE; /* call us again */ +} + +gboolean +gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data) +{ + GIOChannel *ioc; + int flags; + + if (!isatty (STDIN_FILENO)) { + GST_INFO ("stdin is not connected to a terminal"); + return FALSE; + } + + if (io_watch_id > 0) { + g_source_remove (io_watch_id); + io_watch_id = 0; + } + + if (kb_func == NULL && term_settings_saved) { + /* restore terminal settings */ + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &term_settings) == 0) + term_settings_saved = FALSE; + else + g_warning ("could not restore terminal attributes"); + + setvbuf (stdin, NULL, _IOLBF, 0); + } + + if (kb_func != NULL) { + struct termios new_settings; + + if (!term_settings_saved) { + if (tcgetattr (STDIN_FILENO, &term_settings) != 0) { + g_warning ("could not save terminal attributes"); + return FALSE; + } + term_settings_saved = TRUE; + + /* Echo off, canonical mode off, extended input processing off */ + new_settings = term_settings; + new_settings.c_lflag &= ~(ECHO | ICANON | IEXTEN); + + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new_settings) != 0) { + g_warning ("Could not set terminal state"); + return FALSE; + } + setvbuf (stdin, NULL, _IONBF, 0); + } + } + + ioc = g_io_channel_unix_new (STDIN_FILENO); + + /* make non-blocking */ + flags = g_io_channel_get_flags (ioc); + g_io_channel_set_flags (ioc, flags | G_IO_FLAG_NONBLOCK, NULL); + + io_watch_id = g_io_add_watch_full (ioc, G_PRIORITY_DEFAULT, G_IO_IN, + (GIOFunc) gst_play_kb_io_cb, user_data, NULL); + g_io_channel_unref (ioc); + + kb_callback = kb_func; + kb_callback_data = user_data; + + return TRUE; +} + +#else /* !G_OS_UNIX */ + +gboolean +gst_play_kb_set_key_handler (GstPlayKbFunc key_func, gpointer user_data) +{ + GST_FIXME ("Keyboard handling for this OS needs to be implemented"); + return FALSE; +} + +#endif /* !G_OS_UNIX */ diff --git a/playback/player/gst-play/gst-play-kb.h b/playback/player/gst-play/gst-play-kb.h new file mode 100644 index 0000000000..7dab0edd77 --- /dev/null +++ b/playback/player/gst-play/gst-play-kb.h @@ -0,0 +1,35 @@ +/* GStreamer command line playback testing utility - keyboard handling helpers + * + * Copyright (C) 2013 Tim-Philipp Müller + * Copyright (C) 2013 Centricular Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef __GST_PLAY_KB_INCLUDED__ +#define __GST_PLAY_KB_INCLUDED__ + +#include + +#define GST_PLAY_KB_ARROW_UP "\033[A" +#define GST_PLAY_KB_ARROW_DOWN "\033[B" +#define GST_PLAY_KB_ARROW_RIGHT "\033[C" +#define GST_PLAY_KB_ARROW_LEFT "\033[D" + +typedef void (*GstPlayKbFunc) (const gchar * kb_input, gpointer user_data); + +gboolean gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data); + +#endif /* __GST_PLAY_KB_INCLUDED__ */ diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c new file mode 100644 index 0000000000..b5572d61b6 --- /dev/null +++ b/playback/player/gst-play/gst-play.c @@ -0,0 +1,514 @@ +/* GStreamer command line playback testing utility + * + * Copyright (C) 2013-2014 Tim-Philipp Müller + * Copyright (C) 2013 Collabora Ltd. + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include +#include + +#include "gst-play-kb.h" +#include "gstplayer.h" + +#define VOLUME_STEPS 20 + +GST_DEBUG_CATEGORY (play_debug); +#define GST_CAT_DEFAULT play_debug + +typedef struct +{ + gchar **uris; + guint num_uris; + gint cur_idx; + + GstPlayer *player; + GstState desired_state; + + GMainLoop *loop; +} GstPlay; + +static gboolean play_next (GstPlay * play); +static gboolean play_prev (GstPlay * play); +static void play_reset (GstPlay * play); +static void play_set_relative_volume (GstPlay * play, gdouble volume_step); + +static void +end_of_stream_cb (GstPlayer * player, GstPlay * play) +{ + g_print ("\n"); + /* and switch to next item in list */ + if (!play_next (play)) { + g_print ("Reached end of play list.\n"); + g_main_loop_quit (play->loop); + } +} + +static void +error_cb (GstPlayer * player, GError * err, GstPlay * play) +{ + g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); + + /* try next item in list then */ + if (!play_next (play)) { + g_print ("Reached end of play list.\n"); + g_main_loop_quit (play->loop); + } +} + +static gboolean +position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) +{ + GstClockTime dur = -1; + gchar status[64] = { 0, }; + + g_object_get (play->player, "duration", &dur, NULL); + + if (play->desired_state == GST_STATE_PAUSED) + g_snprintf (status, sizeof (status), "Paused"); + else + memset (status, ' ', sizeof (status) - 1); + + if (pos >= 0 && dur > 0) { + gchar dstr[32], pstr[32]; + + /* FIXME: pretty print in nicer format */ + g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); + pstr[9] = '\0'; + g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur)); + dstr[9] = '\0'; + g_print ("%s / %s %s\r", pstr, dstr, status); + } + + return TRUE; +} + +static GstPlay * +play_new (gchar ** uris, gdouble initial_volume) +{ + GstPlay *play; + + play = g_new0 (GstPlay, 1); + + play->uris = uris; + play->num_uris = g_strv_length (uris); + play->cur_idx = -1; + + play->player = gst_player_new (TRUE); + + g_signal_connect (play->player, "position-updated", + G_CALLBACK (position_updated_cb), play); + g_signal_connect (play->player, "end-of-stream", + G_CALLBACK (end_of_stream_cb), play); + g_signal_connect (play->player, "error", G_CALLBACK (error_cb), play); + + play->loop = g_main_loop_new (NULL, FALSE); + play->desired_state = GST_STATE_PLAYING; + + play_set_relative_volume (play, initial_volume - 1.0); + + return play; +} + +static void +play_free (GstPlay * play) +{ + play_reset (play); + + g_object_unref (play->player); + + g_main_loop_unref (play->loop); + + g_strfreev (play->uris); + g_free (play); +} + +/* reset for new file/stream */ +static void +play_reset (GstPlay * play) +{ +} + +static void +play_set_relative_volume (GstPlay * play, gdouble volume_step) +{ + gdouble volume; + + g_object_get (play->player, "volume", &volume, NULL); + volume = round ((volume + volume_step) * VOLUME_STEPS) / VOLUME_STEPS; + volume = CLAMP (volume, 0.0, 10.0); + + g_object_set (play->player, "volume", volume, NULL); + + g_print ("Volume: %.0f%% \n", volume * 100); +} + +static gchar * +play_uri_get_display_name (GstPlay * play, const gchar * uri) +{ + gchar *loc; + + if (gst_uri_has_protocol (uri, "file")) { + loc = g_filename_from_uri (uri, NULL, NULL); + } else if (gst_uri_has_protocol (uri, "pushfile")) { + loc = g_filename_from_uri (uri + 4, NULL, NULL); + } else { + loc = g_strdup (uri); + } + + /* Maybe additionally use glib's filename to display name function */ + return loc; +} + +static void +play_uri (GstPlay * play, const gchar * next_uri) +{ + gchar *loc; + + play_reset (play); + + loc = play_uri_get_display_name (play, next_uri); + g_print ("Now playing %s\n", loc); + g_free (loc); + + g_object_set (play->player, "uri", next_uri, NULL); + gst_player_play (play->player); +} + +/* returns FALSE if we have reached the end of the playlist */ +static gboolean +play_next (GstPlay * play) +{ + if ((play->cur_idx + 1) >= play->num_uris) + return FALSE; + + play_uri (play, play->uris[++play->cur_idx]); + return TRUE; +} + +/* returns FALSE if we have reached the beginning of the playlist */ +static gboolean +play_prev (GstPlay * play) +{ + if (play->cur_idx == 0 || play->num_uris <= 1) + return FALSE; + + play_uri (play, play->uris[--play->cur_idx]); + return TRUE; +} + +static void +do_play (GstPlay * play) +{ + gint i; + + /* dump playlist */ + for (i = 0; i < play->num_uris; ++i) + GST_INFO ("%4u : %s", i, play->uris[i]); + + if (!play_next (play)) + return; + + g_main_loop_run (play->loop); +} + +static void +add_to_playlist (GPtrArray * playlist, const gchar * filename) +{ + GDir *dir; + gchar *uri; + + if (gst_uri_is_valid (filename)) { + g_ptr_array_add (playlist, g_strdup (filename)); + return; + } + + if ((dir = g_dir_open (filename, 0, NULL))) { + const gchar *entry; + + /* FIXME: sort entries for each directory? */ + while ((entry = g_dir_read_name (dir))) { + gchar *path; + + path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL); + add_to_playlist (playlist, path); + g_free (path); + } + + g_dir_close (dir); + return; + } + + uri = gst_filename_to_uri (filename, NULL); + if (uri != NULL) + g_ptr_array_add (playlist, uri); + else + g_warning ("Could not make URI out of filename '%s'", filename); +} + +static void +shuffle_uris (gchar ** uris, guint num) +{ + gchar *tmp; + guint i, j; + + if (num < 2) + return; + + for (i = 0; i < num; i++) { + /* gets equally distributed random number in 0..num-1 [0;num[ */ + j = g_random_int_range (0, num); + tmp = uris[j]; + uris[j] = uris[i]; + uris[i] = tmp; + } +} + +static void +restore_terminal (void) +{ + gst_play_kb_set_key_handler (NULL, NULL); +} + +static void +toggle_paused (GstPlay * play) +{ + if (play->desired_state == GST_STATE_PLAYING) + play->desired_state = GST_STATE_PAUSED; + else + play->desired_state = GST_STATE_PLAYING; + + if (play->desired_state == GST_STATE_PLAYING) + gst_player_play (play->player); + else + gst_player_pause (play->player); +} + +static void +relative_seek (GstPlay * play, gdouble percent) +{ + gint64 dur = -1, pos = -1; + + g_return_if_fail (percent >= -1.0 && percent <= 1.0); + + g_object_get (play->player, "position", &pos, "duration", &dur, NULL); + + if (dur <= 0) + goto seek_failed; + + pos = pos + dur * percent; + if (pos > dur) { + if (!play_next (play)) { + g_print ("\nReached end of play list.\n"); + g_main_loop_quit (play->loop); + } + } else { + if (pos < 0) + pos = 0; + gst_player_seek (play->player, pos); + } + + return; + +seek_failed: + { + g_print ("\nCould not seek.\n"); + } +} + +static void +keyboard_cb (const gchar * key_input, gpointer user_data) +{ + GstPlay *play = (GstPlay *) user_data; + + switch (g_ascii_tolower (key_input[0])) { + case ' ': + toggle_paused (play); + break; + case 'q': + case 'Q': + g_main_loop_quit (play->loop); + break; + case '>': + if (!play_next (play)) { + g_print ("\nReached end of play list.\n"); + g_main_loop_quit (play->loop); + } + break; + case '<': + play_prev (play); + break; + case 27: /* ESC */ + if (key_input[1] == '\0') { + g_main_loop_quit (play->loop); + break; + } + /* fall through */ + default: + if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) { + relative_seek (play, +0.08); + } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) { + relative_seek (play, -0.01); + } else if (strcmp (key_input, GST_PLAY_KB_ARROW_UP) == 0) { + play_set_relative_volume (play, +1.0 / VOLUME_STEPS); + } else if (strcmp (key_input, GST_PLAY_KB_ARROW_DOWN) == 0) { + play_set_relative_volume (play, -1.0 / VOLUME_STEPS); + } else { + GST_INFO ("keyboard input:"); + for (; *key_input != '\0'; ++key_input) + GST_INFO (" code %3d", *key_input); + } + break; + } +} + +int +main (int argc, char **argv) +{ + GstPlay *play; + GPtrArray *playlist; + gboolean print_version = FALSE; + gboolean interactive = FALSE; /* FIXME: maybe enable by default? */ + gboolean shuffle = FALSE; + gdouble volume = 1.0; + gchar **filenames = NULL; + gchar **uris; + guint num, i; + GError *err = NULL; + GOptionContext *ctx; + gchar *playlist_file = NULL; + GOptionEntry options[] = { + {"version", 0, 0, G_OPTION_ARG_NONE, &print_version, + "Print version information and exit", NULL}, + {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle, + "Shuffle playlist", NULL}, + {"interactive", 0, 0, G_OPTION_ARG_NONE, &interactive, + "Interactive control via keyboard", NULL}, + {"volume", 0, 0, G_OPTION_ARG_DOUBLE, &volume, + "Volume", NULL}, + {"playlist", 0, 0, G_OPTION_ARG_FILENAME, &playlist_file, + "Playlist file containing input media files", NULL}, + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, + {NULL} + }; + + g_set_prgname ("gst-play"); + + ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + return 1; + } + g_option_context_free (ctx); + + if (print_version) { + gchar *version_str; + + version_str = gst_version_string (); + g_print ("%s version %s\n", g_get_prgname (), "1.0"); + g_print ("%s\n", version_str); + g_free (version_str); + + g_free (playlist_file); + + return 0; + } + + playlist = g_ptr_array_new (); + + if (playlist_file != NULL) { + gchar *playlist_contents = NULL; + gchar **lines = NULL; + + if (g_file_get_contents (playlist_file, &playlist_contents, NULL, &err)) { + lines = g_strsplit (playlist_contents, "\n", 0); + num = g_strv_length (lines); + + for (i = 0; i < num; i++) { + if (lines[i][0] != '\0') { + GST_LOG ("Playlist[%d]: %s", i + 1, lines[i]); + add_to_playlist (playlist, lines[i]); + } + } + g_strfreev (lines); + g_free (playlist_contents); + } else { + g_printerr ("Could not read playlist: %s\n", err->message); + g_clear_error (&err); + } + g_free (playlist_file); + playlist_file = NULL; + } + + if (playlist->len == 0 && (filenames == NULL || *filenames == NULL)) { + g_printerr ("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...", + "gst-play"); + g_printerr ("\n\n"), + g_printerr ("%s\n\n", + "You must provide at least one filename or URI to play."); + /* No input provided. Free array */ + g_ptr_array_free (playlist, TRUE); + + return 1; + } + + /* fill playlist */ + if (filenames != NULL && *filenames != NULL) { + num = g_strv_length (filenames); + for (i = 0; i < num; ++i) { + GST_LOG ("command line argument: %s", filenames[i]); + add_to_playlist (playlist, filenames[i]); + } + g_strfreev (filenames); + } + + num = playlist->len; + g_ptr_array_add (playlist, NULL); + + uris = (gchar **) g_ptr_array_free (playlist, FALSE); + + if (shuffle) + shuffle_uris (uris, num); + + /* prepare */ + play = play_new (uris, volume); + + if (interactive) { + if (gst_play_kb_set_key_handler (keyboard_cb, play)) { + atexit (restore_terminal); + } else { + g_print ("Interactive keyboard handling in terminal not available.\n"); + } + } + + /* play */ + do_play (play); + + /* clean up */ + play_free (play); + + g_print ("\n"); + return 0; +} From 153efa0077b1c1eceb191e7e5024874a0af61d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 27 Jul 2014 18:10:49 +0200 Subject: [PATCH 014/412] playback/player: Add a simple Makefile for gst-play --- playback/player/gst-play/Makefile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 playback/player/gst-play/Makefile diff --git a/playback/player/gst-play/Makefile b/playback/player/gst-play/Makefile new file mode 100644 index 0000000000..6044c42880 --- /dev/null +++ b/playback/player/gst-play/Makefile @@ -0,0 +1,21 @@ +CFLAGS ?= -Wall -O2 -g +PKGCONFIG ?= pkg-config +LIBS := $(shell $(PKGCONFIG) --libs --cflags gstreamer-1.0 gstreamer-video-1.0) -lm + +all: gst-play + +clean: + rm -f gst-play + +SOURCES = \ + gst-play.c \ + gst-play-kb.c \ + ../lib/gstplayer.c + +HEADERS = \ + gst-play-kb.h \ + ../lib/gstplayer.h + +gst-play: $(SOURCES) $(HEADERS) + $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib + From 10eabda41b057b493887b5207b2970aa87a69529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 28 Jul 2014 12:08:12 +0200 Subject: [PATCH 015/412] playback/player: Add debug logging --- playback/player/gst-play/gst-play.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index b5572d61b6..7981146549 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -135,7 +135,7 @@ play_free (GstPlay * play) { play_reset (play); - g_object_unref (play->player); + gst_object_unref (play->player); g_main_loop_unref (play->loop); @@ -423,6 +423,8 @@ main (int argc, char **argv) } g_option_context_free (ctx); + GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play"); + if (print_version) { gchar *version_str; From be4f88ef41b685b2b960ff7c47083cfa48b53197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 28 Jul 2014 20:09:55 +0200 Subject: [PATCH 016/412] playback/player: Add start of an Android player --- playback/player/android/AndroidManifest.xml | 79 +++ playback/player/android/jni/Android.mk | 24 + playback/player/android/jni/player.c | 489 ++++++++++++++++++ .../src/org/freedesktop/gstreamer/Player.java | 216 ++++++++ .../player/GStreamerSurfaceView.java | 105 ++++ .../freedesktop/gstreamer/player/Play.java | 196 +++++++ 6 files changed, 1109 insertions(+) create mode 100644 playback/player/android/AndroidManifest.xml create mode 100644 playback/player/android/jni/Android.mk create mode 100644 playback/player/android/jni/player.c create mode 100644 playback/player/android/src/org/freedesktop/gstreamer/Player.java create mode 100644 playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java create mode 100644 playback/player/android/src/org/freedesktop/gstreamer/player/Play.java diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/AndroidManifest.xml new file mode 100644 index 0000000000..8b156d49d0 --- /dev/null +++ b/playback/player/android/AndroidManifest.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk new file mode 100644 index 0000000000..378685fd02 --- /dev/null +++ b/playback/player/android/jni/Android.mk @@ -0,0 +1,24 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := gstplayer +LOCAL_SRC_FILES := player.c ../../lib/gstplayer.c +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog -landroid +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ + +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 + +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c new file mode 100644 index 0000000000..b77d170ab0 --- /dev/null +++ b/playback/player/android/jni/player.c @@ -0,0 +1,489 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "../../lib/gstplayer.h" + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to Player, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +typedef struct _Player +{ + jobject java_player; + GstPlayer *player; + ANativeWindow *native_window; +} Player; + +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID native_player_field_id; +static jmethodID on_position_updated_method_id; +static jmethodID on_duration_changed_method_id; +static jmethodID on_end_of_stream_method_id; +static jmethodID on_seek_finished_method_id; +static jmethodID on_error_method_id; +static jmethodID on_video_dimensions_changed_method_id; + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* + * Java Bindings + */ +static void +on_position_updated (GstPlayer * unused, GstClockTime position, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, + on_position_updated_method_id, position); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +on_duration_changed (GstPlayer * unused, GstClockTime duration, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, + on_duration_changed_method_id, duration); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +on_end_of_stream (GstPlayer * unused, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, on_end_of_stream_method_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +on_seek_finished (GstPlayer * unused, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, on_seek_finished_method_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +on_error (GstPlayer * unused, GError * err, Player * player) +{ + JNIEnv *env = get_jni_env (); + jstring error_msg; + + // FIXME + error_msg = err ? (*env)->NewStringUTF (env, err->message) : NULL; + + (*env)->CallVoidMethod (env, player->java_player, on_error_method_id, + error_msg); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } + + (*env)->DeleteLocalRef (env, error_msg); +} + +static void +on_video_dimensions_changed (GstPlayer * unused, gint width, gint height, + Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, + on_video_dimensions_changed_method_id, width, height); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +native_new (JNIEnv * env, jobject thiz) +{ + Player *player = g_slice_new0 (Player); + + player->player = gst_player_new (FALSE); + SET_CUSTOM_DATA (env, thiz, native_player_field_id, player); + player->java_player = (*env)->NewGlobalRef (env, thiz); + + g_signal_connect (player->player, "position-updated", + G_CALLBACK (on_position_updated), player); + g_signal_connect (player->player, "duration-changed", + G_CALLBACK (on_duration_changed), player); + g_signal_connect (player->player, "end-of-stream", + G_CALLBACK (on_end_of_stream), player); + g_signal_connect (player->player, "seek-finished", + G_CALLBACK (on_seek_finished), player); + g_signal_connect (player->player, "error", G_CALLBACK (on_error), player); + g_signal_connect (player->player, "video-dimensions-changed", + G_CALLBACK (on_video_dimensions_changed), player); +} + +static void +native_free (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + (*env)->DeleteGlobalRef (env, player->java_player); + g_slice_free (Player, player); + SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL); +} + +static void +native_play (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + gst_player_play (player->player); +} + +static void +native_pause (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + gst_player_pause (player->player); +} + +static void +native_stop (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + gst_player_stop (player->player); +} + +static void +native_seek (JNIEnv * env, jobject thiz, jlong position) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + gst_player_seek (player->player, position); +} + +static void +native_set_uri (JNIEnv * env, jobject thiz, jobject uri) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + const gchar *uri_str; + + if (!player) + return; + + uri_str = (*env)->GetStringUTFChars (env, uri, NULL); + g_object_set (player->player, "uri", uri_str, NULL); + (*env)->ReleaseStringUTFChars (env, uri, uri_str); +} + +static jobject +native_get_uri (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jobject uri; + gchar *uri_str; + + if (!player) + return NULL; + + g_object_get (player->player, "uri", &uri_str, NULL); + + uri = (*env)->NewStringUTF (env, uri_str); + g_free (uri_str); + + return uri; +} + +static jboolean +native_is_playing (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jboolean is_playing; + + if (!player) + return FALSE; + + g_object_get (player->player, "is-playing", &is_playing, NULL); + + return is_playing; +} + +static jlong +native_get_position (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jdouble position; + + if (!player) + return -1; + + g_object_get (player->player, "position", &position, NULL); + + return position; +} + +static jlong +native_get_duration (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jlong duration; + + if (!player) + return -1; + + g_object_get (player->player, "duration", &duration, NULL); + + return duration; +} + +static jdouble +native_get_volume (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jdouble volume; + + if (!player) + return 1.0; + + g_object_get (player->player, "volume", &volume, NULL); + + return volume; +} + +static void +native_set_volume (JNIEnv * env, jobject thiz, jdouble volume) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + g_object_set (player->player, "volume", volume, NULL); +} + +static jboolean +native_get_mute (JNIEnv * env, jobject thiz) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + jboolean mute; + + if (!player) + return FALSE; + + g_object_get (player->player, "mute", &mute, NULL); + + return mute; +} + +static void +native_set_mute (JNIEnv * env, jobject thiz, jboolean mute) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + + if (!player) + return; + + g_object_set (player->player, "mute", mute, NULL); +} + +static void +native_set_surface (JNIEnv * env, jobject thiz, jobject surface) +{ + Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); + ANativeWindow *new_native_window; + + if (!player) + return; + + new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL; + GST_DEBUG ("Received surface %p (native window %p)", surface, + new_native_window); + + if (player->native_window) { + ANativeWindow_release (player->native_window); + } + + player->native_window = new_native_window; + g_object_set (player->player, "window-handle", (gpointer) new_native_window, + NULL); +} + +static void +native_class_init (JNIEnv * env, jclass klass) +{ + native_player_field_id = + (*env)->GetFieldID (env, klass, "native_player", "J"); + on_position_updated_method_id = + (*env)->GetMethodID (env, klass, "onPositionUpdated", "(J)V"); + on_duration_changed_method_id = + (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V"); + on_end_of_stream_method_id = + (*env)->GetMethodID (env, klass, "onEndOfStream", "()V"); + on_seek_finished_method_id = + (*env)->GetMethodID (env, klass, "onSeekFinished", "()V"); + on_error_method_id = + (*env)->GetMethodID (env, klass, "onError", "(Ljava/lang/String;)V"); + on_video_dimensions_changed_method_id = + (*env)->GetMethodID (env, klass, "onVideoDimensionsChanged", "(II)V"); + + if (!native_player_field_id || + !on_position_updated_method_id || !on_duration_changed_method_id || + !on_end_of_stream_method_id || !on_seek_finished_method_id || + !on_error_method_id || !on_video_dimensions_changed_method_id) { + static const gchar *message = + "The calling class does not implement all necessary interface methods"; + jclass exception_class = (*env)->FindClass (env, "java/lang/Exception"); + __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message); + (*env)->ThrowNew (env, exception_class, message); + } + + gst_debug_set_threshold_for_name ("gst-player", GST_LEVEL_TRACE); +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeClassInit", "()V", (void *) native_class_init}, + {"nativeNew", "()V", (void *) native_new}, + {"nativePlay", "()V", (void *) native_play}, + {"nativePause", "()V", (void *) native_pause}, + {"nativeSeek", "(J)V", (void *) native_seek}, + {"nativeFree", "()V", (void *) native_free}, + {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri}, + {"nativeSetUri", "(Ljava/lang/String;)V", (void *) native_set_uri}, + {"nativeIsPlaying", "()Z", (void *) native_is_playing}, + {"nativeGetPosition", "()J", (void *) native_get_position}, + {"nativeGetDuration", "()J", (void *) native_get_duration}, + {"nativeGetVolume", "()D", (void *) native_get_volume}, + {"nativeSetVolume", "(D)V", (void *) native_set_volume}, + {"nativeGetMute", "()Z", (void *) native_get_mute}, + {"nativeSetMute", "(Z)V", (void *) native_set_mute}, + {"nativeSetSurface", "(Landroid/view/Surface;)V", + (void *) native_set_surface} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/Player"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java new file mode 100644 index 0000000000..862444ba03 --- /dev/null +++ b/playback/player/android/src/org/freedesktop/gstreamer/Player.java @@ -0,0 +1,216 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +package org.freedesktop.gstreamer; + +import java.io.Closeable; +import android.view.Surface; +import android.content.Context; +import org.freedesktop.gstreamer.GStreamer; + +public class Player implements Closeable { + private static native void nativeClassInit(); + public static void init(Context context) throws Exception { + System.loadLibrary("gstreamer_android"); + GStreamer.init(context); + + System.loadLibrary("gstplayer"); + nativeClassInit(); + } + + private long native_player; + private native void nativeNew(); + public Player() { + nativeNew(); + } + + private native void nativeFree(); + @Override + public void close() { + nativeFree(); + } + + private native void nativePlay(); + public void play() { + nativePlay(); + } + + private native void nativePause(); + public void pause() { + nativePause(); + } + + private native void nativeStop(); + public void stop() { + nativeStop(); + } + + private native void nativeSeek(long position); + public void seek(long position) { + nativeSeek(position); + } + + private native String nativeGetUri(); + public String getUri() { + return nativeGetUri(); + } + + private native void nativeSetUri(String uri); + public void setUri(String uri) { + nativeSetUri(uri); + } + + private native boolean nativeIsPlaying(); + public boolean isPlaying() { + return nativeIsPlaying(); + } + + private native long nativeGetPosition(); + public long getPosition() { + return nativeGetPosition(); + } + + private native long nativeGetDuration(); + public long getDuration() { + return nativeGetDuration(); + } + + private native double nativeGetVolume(); + public double getVolume() { + return nativeGetVolume(); + } + + private native void nativeSetVolume(double volume); + public void setVolume(double volume) { + nativeSetVolume(volume); + } + + private native boolean nativeGetMute(); + public boolean getMute() { + return nativeGetMute(); + } + + private native void nativeSetMute(boolean mute); + public void setMute(boolean mute) { + nativeSetMute(mute); + } + + private Surface surface; + private native void nativeSetSurface(Surface surface); + public void setSurface(Surface surface) { + this.surface = surface; + nativeSetSurface(surface); + } + + public Surface getSurface() { + return surface; + } + + public static interface PositionUpdatedListener { + abstract void positionUpdated(Player player, long position); + } + + private PositionUpdatedListener positionUpdatedListener; + public void setPositionUpdatedListener(PositionUpdatedListener listener) { + positionUpdatedListener = listener; + } + + private void onPositionUpdated(long position) { + if (positionUpdatedListener != null) { + positionUpdatedListener.positionUpdated(this, position); + } + } + + public static interface DurationChangedListener { + abstract void durationChanged(Player player, long duration); + } + + private DurationChangedListener durationChangedListener; + public void setDurationChangedListener(DurationChangedListener listener) { + durationChangedListener = listener; + } + + private void onDurationChanged(long duration) { + if (durationChangedListener != null) { + durationChangedListener.durationChanged(this, duration); + } + } + + public static interface EndOfStreamListener { + abstract void endOfStream(Player player); + } + + private EndOfStreamListener endOfStreamListener; + public void setEndOfStreamListener(EndOfStreamListener listener) { + endOfStreamListener = listener; + } + + private void onEndOfStream() { + if (endOfStreamListener != null) { + endOfStreamListener.endOfStream(this); + } + } + + public static interface SeekFinishedListener { + abstract void seekFinished(Player player); + } + + private SeekFinishedListener seekFinishedListener; + public void setSeekFinishedListener(SeekFinishedListener listener) { + seekFinishedListener = listener; + } + + private void onSeekFinished() { + if (seekFinishedListener != null) { + seekFinishedListener.seekFinished(this); + } + } + + public static interface ErrorListener { + // FIXME: enums + abstract void error(Player player, String errorMessage); + } + + private ErrorListener errorListener; + public void setErrorListener(ErrorListener listener) { + errorListener = listener; + } + + private void onError(String errorMessage) { + if (errorListener != null) { + errorListener.error(this, errorMessage); + } + } + + public static interface VideoDimensionsChangedListener { + abstract void videoDimensionsChanged(Player player, int width, int height); + } + + private VideoDimensionsChangedListener videoDimensionsChangedListener; + public void setVideoDimensionsChangedListener(VideoDimensionsChangedListener listener) { + videoDimensionsChangedListener = listener; + } + + private void onVideoDimensionsChanged(int width, int height) { + if (videoDimensionsChangedListener != null) { + videoDimensionsChangedListener.videoDimensionsChanged(this, width, height); + } + } +} diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java new file mode 100644 index 0000000000..f2dd8a9bf0 --- /dev/null +++ b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java @@ -0,0 +1,105 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +package org.freedesktop.gstreamer.play; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; + +// A simple SurfaceView whose width and height can be set from the outside +public class GStreamerSurfaceView extends SurfaceView { + public int media_width = 320; + public int media_height = 240; + + // Mandatory constructors, they do not do much + public GStreamerSurfaceView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public GStreamerSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GStreamerSurfaceView (Context context) { + super(context); + } + + // Called by the layout manager to find out our size and give us some rules. + // We will try to maximize our size, and preserve the media's aspect ratio if + // we are given the freedom to do so. + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0, height = 0; + int wmode = View.MeasureSpec.getMode(widthMeasureSpec); + int hmode = View.MeasureSpec.getMode(heightMeasureSpec); + int wsize = View.MeasureSpec.getSize(widthMeasureSpec); + int hsize = View.MeasureSpec.getSize(heightMeasureSpec); + + Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height); + // Obey width rules + switch (wmode) { + case View.MeasureSpec.AT_MOST: + if (hmode == View.MeasureSpec.EXACTLY) { + width = Math.min(hsize * media_width / media_height, wsize); + break; + } + case View.MeasureSpec.EXACTLY: + width = wsize; + break; + case View.MeasureSpec.UNSPECIFIED: + width = media_width; + } + + // Obey height rules + switch (hmode) { + case View.MeasureSpec.AT_MOST: + if (wmode == View.MeasureSpec.EXACTLY) { + height = Math.min(wsize * media_height / media_width, hsize); + break; + } + case View.MeasureSpec.EXACTLY: + height = hsize; + break; + case View.MeasureSpec.UNSPECIFIED: + height = media_height; + } + + // Finally, calculate best size when both axis are free + if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) { + int correct_height = width * media_height / media_width; + int correct_width = height * media_width / media_height; + + if (correct_height < height) + height = correct_height; + else + width = correct_width; + } + + // Obey minimum size + width = Math.max (getSuggestedMinimumWidth(), width); + height = Math.max (getSuggestedMinimumHeight(), height); + setMeasuredDimension(width, height); + } + +} diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java new file mode 100644 index 0000000000..2874f05de5 --- /dev/null +++ b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java @@ -0,0 +1,196 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +package org.freedesktop.gstreamer.play; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.PowerManager; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.Player; + +public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener { + private PowerManager.WakeLock wake_lock; + private Player player; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + try { + Player.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + player = new Player(); + + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GStreamer Play"); + wake_lock.setReferenceCounted(false); + + ImageButton play = (ImageButton) this.findViewById(R.id.button_play); + play.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + player.play(); + wake_lock.acquire(); + } + }); + + ImageButton pause = (ImageButton) this.findViewById(R.id.button_pause); + pause.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + player.pause(); + wake_lock.release(); + } + }); + + final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); + sb.setOnSeekBarChangeListener(this); + + player.setPositionUpdatedListener(new Player.PositionUpdatedListener() { + public void positionUpdated(Player player, final long position) { + runOnUiThread (new Runnable() { + public void run() { + sb.setProgress((int) (position / 1000000)); + updateTimeWidget(); + } + }); + } + }); + + player.setDurationChangedListener(new Player.DurationChangedListener() { + public void durationChanged(Player player, final long duration) { + runOnUiThread (new Runnable() { + public void run() { + sb.setMax((int) (duration / 1000000)); + updateTimeWidget(); + } + }); + } + }); + + final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video); + player.setVideoDimensionsChangedListener(new Player.VideoDimensionsChangedListener() { + public void videoDimensionsChanged(Player player, final int width, final int height) { + runOnUiThread (new Runnable() { + public void run() { + Log.i ("GStreamer", "Media size changed to " + width + "x" + height); + gsv.media_width = width; + gsv.media_height = height; + runOnUiThread(new Runnable() { + public void run() { + gsv.requestLayout(); + } + }); + } + }); + } + }); + + SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); + SurfaceHolder sh = sv.getHolder(); + sh.addCallback(this); + + String mediaUri = null; + Intent intent = getIntent(); + android.net.Uri uri = intent.getData(); + Log.i ("GStreamer", "Received URI: " + uri); + if (uri.getScheme().equals("content")) { + android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null); + cursor.moveToFirst(); + mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA)); + cursor.close(); + } else { + mediaUri = uri.toString(); + } + player.setUri(mediaUri); + + updateTimeWidget(); + } + + protected void onDestroy() { + player.close(); + super.onDestroy(); + } + + private void updateTimeWidget () { + final TextView tv = (TextView) this.findViewById(R.id.textview_time); + final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); + final int pos = sb.getProgress(); + final int max = sb.getMax(); + + SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + final String message = df.format(new Date (pos)) + " / " + df.format(new Date (max)); + tv.setText(message); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + + width + " height " + height); + player.setSurface(holder.getSurface()); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.d("GStreamer", "Surface created: " + holder.getSurface()); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d("GStreamer", "Surface destroyed"); + player.setSurface(null); + } + + public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) { + if (!fromUser) return; + + updateTimeWidget(); + } + + public void onStartTrackingTouch(SeekBar sb) { + } + + public void onStopTrackingTouch(SeekBar sb) { + Log.d("GStreamer", "Seek to " + sb.getProgress()); + player.seek(((long) sb.getProgress()) * 1000000); + } +} From 3352d5aee08ca42367746388e1eb46abe195e2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 30 Jul 2014 16:18:01 +0200 Subject: [PATCH 017/412] playback/player: Add GTK+ sample application --- playback/player/gtk/Makefile | 19 +++ playback/player/gtk/gtk-play.c | 216 +++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 playback/player/gtk/Makefile create mode 100644 playback/player/gtk/gtk-play.c diff --git a/playback/player/gtk/Makefile b/playback/player/gtk/Makefile new file mode 100644 index 0000000000..0fffb372d4 --- /dev/null +++ b/playback/player/gtk/Makefile @@ -0,0 +1,19 @@ +CFLAGS ?= -Wall -O2 -g +PKGCONFIG ?= pkg-config +LIBS := $(shell $(PKGCONFIG) --libs --cflags gstreamer-1.0 gstreamer-video-1.0 gtk+-3.0) -lm + +all: gtk-play + +clean: + rm -f gtk-play + +SOURCES = \ + gtk-play.c \ + ../lib/gstplayer.c + +HEADERS = \ + ../lib/gstplayer.h + +gtk-play: $(SOURCES) $(HEADERS) + $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib + diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c new file mode 100644 index 0000000000..437fc36772 --- /dev/null +++ b/playback/player/gtk/gtk-play.c @@ -0,0 +1,216 @@ +#include + +#include +#include + +#include +#if defined (GDK_WINDOWING_X11) +#include +#elif defined (GDK_WINDOWING_WIN32) +#include +#elif defined (GDK_WINDOWING_QUARTZ) +#include +#endif + +#include + +#include "../lib/gstplayer.h" + +typedef struct +{ + GstPlayer *player; + gchar *uri; + + GtkWidget *window; + GtkWidget *play_button, *pause_button; + GtkWidget *seekbar; + GtkWidget *video_area; + gulong seekbar_value_changed_signal_id; +} GtkPlay; + +static void +delete_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +{ + gst_player_stop (play->player); + gtk_main_quit (); +} + +static void +video_area_realize_cb (GtkWidget * widget, GtkPlay * play) +{ + GdkWindow *window = gtk_widget_get_window (widget); + guintptr window_handle; + + if (!gdk_window_ensure_native (window)) + g_error ("Couldn't create native window needed for GstXOverlay!"); + +#if defined (GDK_WINDOWING_WIN32) + window_handle = (guintptr) GDK_WINDOW_HWND (window); +#elif defined (GDK_WINDOWING_QUARTZ) + window_handle = gdk_quartz_window_get_nsview (window); +#elif defined (GDK_WINDOWING_X11) + window_handle = GDK_WINDOW_XID (window); +#endif + g_object_set (play->player, "window-handle", (gpointer) window_handle, NULL); +} + +static void +play_clicked_cb (GtkButton * button, GtkPlay * play) +{ + gst_player_play (play->player); +} + +static void +pause_clicked_cb (GtkButton * button, GtkPlay * play) +{ + gst_player_pause (play->player); +} + +static void +seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) +{ + gdouble value = gtk_range_get_value (GTK_RANGE (play->seekbar)); + gst_player_seek (play->player, gst_util_uint64_scale (value, GST_SECOND, 1)); +} + +static void +create_ui (GtkPlay * play) +{ + GtkWidget *controls, *main_hbox, *main_vbox; + + play->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (G_OBJECT (play->window), "delete-event", + G_CALLBACK (delete_event_cb), play); + + play->video_area = gtk_drawing_area_new (); + gtk_widget_set_double_buffered (play->video_area, FALSE); + g_signal_connect (play->video_area, "realize", + G_CALLBACK (video_area_realize_cb), play); + + play->play_button = + gtk_button_new_from_icon_name ("media-playback-start", + GTK_ICON_SIZE_BUTTON); + g_signal_connect (G_OBJECT (play->play_button), "clicked", + G_CALLBACK (play_clicked_cb), play); + + play->pause_button = + gtk_button_new_from_icon_name ("media-playback-pause", + GTK_ICON_SIZE_BUTTON); + g_signal_connect (G_OBJECT (play->pause_button), "clicked", + G_CALLBACK (pause_clicked_cb), play); + + play->seekbar = + gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); + gtk_scale_set_draw_value (GTK_SCALE (play->seekbar), 0); + play->seekbar_value_changed_signal_id = + g_signal_connect (G_OBJECT (play->seekbar), "value-changed", + G_CALLBACK (seekbar_value_changed_cb), play); + + controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (controls), play->play_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->pause_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); + + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (main_vbox), controls, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (play->window), main_vbox); + + gtk_widget_realize (play->video_area); + + gtk_widget_show_all (play->window); + + gtk_widget_hide (play->video_area); +} + +static void +play_clear (GtkPlay * play) +{ + g_free (play->uri); + g_object_unref (play->player); +} + +static void +duration_changed_cb (GstPlayer * unused, GstClockTime duration, GtkPlay * play) +{ + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, + (gdouble) duration / GST_SECOND); +} + +static void +position_updated_cb (GstPlayer * unused, GstClockTime position, GtkPlay * play) +{ + g_signal_handler_block (play->seekbar, play->seekbar_value_changed_signal_id); + gtk_range_set_value (GTK_RANGE (play->seekbar), + (gdouble) position / GST_SECOND); + g_signal_handler_unblock (play->seekbar, + play->seekbar_value_changed_signal_id); +} + +static void +video_dimensions_changed_cb (GstPlayer * unused, gint width, gint height, + GtkPlay * play) +{ + gtk_widget_show (play->video_area); +} + +int +main (gint argc, gchar ** argv) +{ + GtkPlay play; + GOptionContext *ctx; + GOptionEntry options[] = { + {NULL} + }; + GError *err = NULL; + + memset (&play, 0, sizeof (play)); + + g_set_prgname ("gtk-play"); + + ctx = g_option_context_new ("FILE|URI"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gtk_get_option_group (TRUE)); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + return 1; + } + g_option_context_free (ctx); + + // FIXME: Add support for playlists and stuff + play.uri = g_strdup (argv[1]); + if (!play.uri) { + g_print ("Usage: %s FILE|URI\n", argv[0]); + return 1; + } + + play.player = gst_player_new (TRUE); + + if (!gst_uri_is_valid (play.uri)) { + gchar *uri = gst_filename_to_uri (play.uri, NULL); + g_free (play.uri); + play.uri = uri; + } + g_object_set (play.player, "uri", play.uri, NULL); + + create_ui (&play); + + g_signal_connect (play.player, "position-updated", + G_CALLBACK (position_updated_cb), &play); + g_signal_connect (play.player, "duration-changed", + G_CALLBACK (duration_changed_cb), &play); + g_signal_connect (play.player, "video-dimensions-changed", + G_CALLBACK (video_dimensions_changed_cb), &play); + + gst_player_pause (play.player); + + gtk_main (); + + play_clear (&play); + + return 0; +} From b34e62884c80ccdf4b0dcd6f71231dc23a6a6f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 2 Aug 2014 20:33:38 +0200 Subject: [PATCH 018/412] playback/player: Move library around into a proper directory --- playback/player/android/jni/Android.mk | 2 +- playback/player/android/jni/player.c | 2 +- playback/player/gst-play/Makefile | 4 ++-- playback/player/gst-play/gst-play.c | 2 +- playback/player/gtk/Makefile | 4 ++-- playback/player/gtk/gtk-play.c | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 378685fd02..39ad84ee4f 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -3,7 +3,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := player.c ../../lib/gstplayer.c +LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index b77d170ab0..e0e2d22913 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "../../lib/gstplayer.h" +#include "../../lib/gst/player/gstplayer.h" GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category diff --git a/playback/player/gst-play/Makefile b/playback/player/gst-play/Makefile index 6044c42880..ff88941a52 100644 --- a/playback/player/gst-play/Makefile +++ b/playback/player/gst-play/Makefile @@ -10,11 +10,11 @@ clean: SOURCES = \ gst-play.c \ gst-play-kb.c \ - ../lib/gstplayer.c + ../lib/gst/player/gstplayer.c HEADERS = \ gst-play-kb.h \ - ../lib/gstplayer.h + ../lib/gst/player/gstplayer.h gst-play: $(SOURCES) $(HEADERS) $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 7981146549..5154d7ac37 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -29,7 +29,7 @@ #include #include "gst-play-kb.h" -#include "gstplayer.h" +#include #define VOLUME_STEPS 20 diff --git a/playback/player/gtk/Makefile b/playback/player/gtk/Makefile index 0fffb372d4..a17c45ed30 100644 --- a/playback/player/gtk/Makefile +++ b/playback/player/gtk/Makefile @@ -9,10 +9,10 @@ clean: SOURCES = \ gtk-play.c \ - ../lib/gstplayer.c + ../lib/gst/player/gstplayer.c HEADERS = \ - ../lib/gstplayer.h + ../lib/gst/player/gstplayer.h gtk-play: $(SOURCES) $(HEADERS) $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 437fc36772..7bbc7a809f 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -14,7 +14,7 @@ #include -#include "../lib/gstplayer.h" +#include typedef struct { From 7071c121b6953fd99ab8593bda23dfa55ca759ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 2 Aug 2014 20:19:09 +0200 Subject: [PATCH 019/412] playback/player: Add iOS app --- .../ios/GstPlay.xcodeproj/project.pbxproj | 437 ++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/GstPlay.xcscheme | 96 ++ .../xcschemes/xcschememanagement.plist | 27 + playback/player/ios/GstPlay/AppDelegate.h | 15 + playback/player/ios/GstPlay/AppDelegate.m | 45 + playback/player/ios/GstPlay/EaglUIVIew.h | 11 + playback/player/ios/GstPlay/EaglUIVIew.m | 13 + .../player/ios/GstPlay/GstPlay-Info.plist | 49 + .../player/ios/GstPlay/GstPlay-Prefix.pch | 16 + .../ios/GstPlay/LibraryViewController.h | 12 + .../ios/GstPlay/LibraryViewController.m | 165 +++ .../GstPlay/MainStoryboard_iPad.storyboard | 180 ++++ .../GstPlay/MainStoryboard_iPhone.storyboard | 185 ++++ playback/player/ios/GstPlay/Ubuntu-R.ttf | Bin 0 -> 353824 bytes .../player/ios/GstPlay/VideoViewController.h | 23 + .../player/ios/GstPlay/VideoViewController.m | 204 ++++ .../ios/GstPlay/en.lproj/InfoPlist.strings | 2 + playback/player/ios/GstPlay/fonts.conf | 126 +++ playback/player/ios/GstPlay/gst_ios_init.h | 39 + playback/player/ios/GstPlay/gst_ios_init.m | 988 ++++++++++++++++++ playback/player/ios/GstPlay/main.m | 12 + 22 files changed, 2652 insertions(+) create mode 100644 playback/player/ios/GstPlay.xcodeproj/project.pbxproj create mode 100644 playback/player/ios/GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/GstPlay.xcscheme create mode 100644 playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 playback/player/ios/GstPlay/AppDelegate.h create mode 100644 playback/player/ios/GstPlay/AppDelegate.m create mode 100644 playback/player/ios/GstPlay/EaglUIVIew.h create mode 100644 playback/player/ios/GstPlay/EaglUIVIew.m create mode 100644 playback/player/ios/GstPlay/GstPlay-Info.plist create mode 100644 playback/player/ios/GstPlay/GstPlay-Prefix.pch create mode 100644 playback/player/ios/GstPlay/LibraryViewController.h create mode 100644 playback/player/ios/GstPlay/LibraryViewController.m create mode 100644 playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard create mode 100644 playback/player/ios/GstPlay/MainStoryboard_iPhone.storyboard create mode 100644 playback/player/ios/GstPlay/Ubuntu-R.ttf create mode 100644 playback/player/ios/GstPlay/VideoViewController.h create mode 100644 playback/player/ios/GstPlay/VideoViewController.m create mode 100644 playback/player/ios/GstPlay/en.lproj/InfoPlist.strings create mode 100644 playback/player/ios/GstPlay/fonts.conf create mode 100644 playback/player/ios/GstPlay/gst_ios_init.h create mode 100644 playback/player/ios/GstPlay/gst_ios_init.m create mode 100644 playback/player/ios/GstPlay/main.m diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..14facf3dd9 --- /dev/null +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -0,0 +1,437 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + AD2B881F198D631B0070367B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B881E198D631B0070367B /* Foundation.framework */; }; + AD2B8821198D631B0070367B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B8820198D631B0070367B /* CoreGraphics.framework */; }; + AD2B8823198D631B0070367B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B8822198D631B0070367B /* UIKit.framework */; }; + AD2B8825198D631B0070367B /* GStreamer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B8824198D631B0070367B /* GStreamer.framework */; }; + AD2B882B198D631B0070367B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AD2B8829198D631B0070367B /* InfoPlist.strings */; }; + AD2B882D198D631B0070367B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B882C198D631B0070367B /* main.m */; }; + AD2B8831198D631B0070367B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8830198D631B0070367B /* AppDelegate.m */; }; + AD2B8833198D631B0070367B /* fonts.conf in Resources */ = {isa = PBXBuildFile; fileRef = AD2B8832198D631B0070367B /* fonts.conf */; }; + AD2B8835198D631B0070367B /* Ubuntu-R.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AD2B8834198D631B0070367B /* Ubuntu-R.ttf */; }; + AD2B8837198D631B0070367B /* gst_ios_init.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8836198D631B0070367B /* gst_ios_init.m */; }; + AD2B8858198D637A0070367B /* EaglUIVIew.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8857198D637A0070367B /* EaglUIVIew.m */; }; + AD2B885B198D65470070367B /* MainStoryboard_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD2B8859198D65470070367B /* MainStoryboard_iPhone.storyboard */; }; + AD2B885C198D65470070367B /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD2B885A198D65470070367B /* MainStoryboard_iPad.storyboard */; }; + AD2B8861198D65780070367B /* LibraryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B885E198D65780070367B /* LibraryViewController.m */; }; + AD2B8862198D65780070367B /* VideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8860198D65780070367B /* VideoViewController.m */; }; + AD2B886C198D69ED0070367B /* gstplayer.c in Sources */ = {isa = PBXBuildFile; fileRef = AD2B886A198D69ED0070367B /* gstplayer.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + AD2B881B198D631B0070367B /* GstPlay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GstPlay.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AD2B881E198D631B0070367B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + AD2B8820198D631B0070367B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + AD2B8822198D631B0070367B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + AD2B8824198D631B0070367B /* GStreamer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GStreamer.framework; path = Library/Frameworks/GStreamer.framework; sourceTree = DEVELOPER_DIR; }; + AD2B8828198D631B0070367B /* GstPlay-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GstPlay-Info.plist"; sourceTree = ""; }; + AD2B882A198D631B0070367B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + AD2B882C198D631B0070367B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AD2B882E198D631B0070367B /* GstPlay-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GstPlay-Prefix.pch"; sourceTree = ""; }; + AD2B882F198D631B0070367B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AD2B8830198D631B0070367B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AD2B8832198D631B0070367B /* fonts.conf */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = fonts.conf; sourceTree = ""; }; + AD2B8834198D631B0070367B /* Ubuntu-R.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Ubuntu-R.ttf"; sourceTree = ""; }; + AD2B8836198D631B0070367B /* gst_ios_init.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = gst_ios_init.m; sourceTree = ""; }; + AD2B8838198D631B0070367B /* gst_ios_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gst_ios_init.h; sourceTree = ""; }; + AD2B8840198D631B0070367B /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + AD2B8856198D637A0070367B /* EaglUIVIew.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EaglUIVIew.h; sourceTree = ""; }; + AD2B8857198D637A0070367B /* EaglUIVIew.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EaglUIVIew.m; sourceTree = ""; }; + AD2B8859198D65470070367B /* MainStoryboard_iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryboard_iPhone.storyboard; sourceTree = ""; }; + AD2B885A198D65470070367B /* MainStoryboard_iPad.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryboard_iPad.storyboard; sourceTree = ""; }; + AD2B885D198D65780070367B /* LibraryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LibraryViewController.h; sourceTree = ""; }; + AD2B885E198D65780070367B /* LibraryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LibraryViewController.m; sourceTree = ""; }; + AD2B885F198D65780070367B /* VideoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoViewController.h; sourceTree = ""; }; + AD2B8860198D65780070367B /* VideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoViewController.m; sourceTree = ""; }; + AD2B886A198D69ED0070367B /* gstplayer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gstplayer.c; sourceTree = ""; }; + AD2B886B198D69ED0070367B /* gstplayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gstplayer.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AD2B8818198D631B0070367B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AD2B8821198D631B0070367B /* CoreGraphics.framework in Frameworks */, + AD2B8825198D631B0070367B /* GStreamer.framework in Frameworks */, + AD2B8823198D631B0070367B /* UIKit.framework in Frameworks */, + AD2B881F198D631B0070367B /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + AD2B8812198D631B0070367B = { + isa = PBXGroup; + children = ( + AD2B8868198D69ED0070367B /* gst */, + AD2B8826198D631B0070367B /* GstPlay */, + AD2B881D198D631B0070367B /* Frameworks */, + AD2B881C198D631B0070367B /* Products */, + ); + sourceTree = ""; + }; + AD2B881C198D631B0070367B /* Products */ = { + isa = PBXGroup; + children = ( + AD2B881B198D631B0070367B /* GstPlay.app */, + ); + name = Products; + sourceTree = ""; + }; + AD2B881D198D631B0070367B /* Frameworks */ = { + isa = PBXGroup; + children = ( + AD2B881E198D631B0070367B /* Foundation.framework */, + AD2B8820198D631B0070367B /* CoreGraphics.framework */, + AD2B8822198D631B0070367B /* UIKit.framework */, + AD2B8824198D631B0070367B /* GStreamer.framework */, + AD2B8840198D631B0070367B /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + AD2B8826198D631B0070367B /* GstPlay */ = { + isa = PBXGroup; + children = ( + AD2B8856198D637A0070367B /* EaglUIVIew.h */, + AD2B8857198D637A0070367B /* EaglUIVIew.m */, + AD2B882F198D631B0070367B /* AppDelegate.h */, + AD2B8830198D631B0070367B /* AppDelegate.m */, + AD2B8859198D65470070367B /* MainStoryboard_iPhone.storyboard */, + AD2B885A198D65470070367B /* MainStoryboard_iPad.storyboard */, + AD2B885D198D65780070367B /* LibraryViewController.h */, + AD2B885E198D65780070367B /* LibraryViewController.m */, + AD2B885F198D65780070367B /* VideoViewController.h */, + AD2B8860198D65780070367B /* VideoViewController.m */, + AD2B8827198D631B0070367B /* Supporting Files */, + ); + path = GstPlay; + sourceTree = ""; + }; + AD2B8827198D631B0070367B /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AD2B8828198D631B0070367B /* GstPlay-Info.plist */, + AD2B8829198D631B0070367B /* InfoPlist.strings */, + AD2B882C198D631B0070367B /* main.m */, + AD2B882E198D631B0070367B /* GstPlay-Prefix.pch */, + AD2B8832198D631B0070367B /* fonts.conf */, + AD2B8834198D631B0070367B /* Ubuntu-R.ttf */, + AD2B8836198D631B0070367B /* gst_ios_init.m */, + AD2B8838198D631B0070367B /* gst_ios_init.h */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + AD2B8868198D69ED0070367B /* gst */ = { + isa = PBXGroup; + children = ( + AD2B8869198D69ED0070367B /* player */, + ); + name = gst; + path = ../lib/gst; + sourceTree = ""; + }; + AD2B8869198D69ED0070367B /* player */ = { + isa = PBXGroup; + children = ( + AD2B886A198D69ED0070367B /* gstplayer.c */, + AD2B886B198D69ED0070367B /* gstplayer.h */, + ); + path = player; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AD2B881A198D631B0070367B /* GstPlay */ = { + isa = PBXNativeTarget; + buildConfigurationList = AD2B8850198D631B0070367B /* Build configuration list for PBXNativeTarget "GstPlay" */; + buildPhases = ( + AD2B8817198D631B0070367B /* Sources */, + AD2B8818198D631B0070367B /* Frameworks */, + AD2B8819198D631B0070367B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GstPlay; + productName = GstPlay; + productReference = AD2B881B198D631B0070367B /* GstPlay.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AD2B8813198D631B0070367B /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0510; + ORGANIZATIONNAME = "Sebastian Dröge"; + }; + buildConfigurationList = AD2B8816198D631B0070367B /* Build configuration list for PBXProject "GstPlay" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = AD2B8812198D631B0070367B; + productRefGroup = AD2B881C198D631B0070367B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AD2B881A198D631B0070367B /* GstPlay */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AD2B8819198D631B0070367B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AD2B885C198D65470070367B /* MainStoryboard_iPad.storyboard in Resources */, + AD2B885B198D65470070367B /* MainStoryboard_iPhone.storyboard in Resources */, + AD2B8835198D631B0070367B /* Ubuntu-R.ttf in Resources */, + AD2B8833198D631B0070367B /* fonts.conf in Resources */, + AD2B882B198D631B0070367B /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AD2B8817198D631B0070367B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AD2B8861198D65780070367B /* LibraryViewController.m in Sources */, + AD2B8831198D631B0070367B /* AppDelegate.m in Sources */, + AD2B8862198D65780070367B /* VideoViewController.m in Sources */, + AD2B8858198D637A0070367B /* EaglUIVIew.m in Sources */, + AD2B882D198D631B0070367B /* main.m in Sources */, + AD2B8837198D631B0070367B /* gst_ios_init.m in Sources */, + AD2B886C198D69ED0070367B /* gstplayer.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + AD2B8829198D631B0070367B /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + AD2B882A198D631B0070367B /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + AD2B884E198D631B0070367B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 6.1; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AD2B884F198D631B0070367B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 6.1; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AD2B8851198D631B0070367B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = armv7; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + FRAMEWORK_SEARCH_PATHS = ( + "~/Library/Developer/GStreamer/iPhone.sdk", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "GstPlay/GstPlay-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"~/Library/Developer/GStreamer/iPhone.sdk/GStreamer.framework/Headers\"", + ../lib, + ); + INFOPLIST_FILE = "GstPlay/GstPlay-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.1; + OTHER_LDFLAGS = ( + "-lresolv", + "-lstdc++", + "-framework", + CoreFoundation, + "-framework", + Foundation, + "-framework", + AVFoundation, + "-framework", + CoreMedia, + "-framework", + CoreVideo, + "-framework", + CoreAudio, + "-framework", + AudioToolbox, + "-framework", + OpenGLES, + "-framework", + AssetsLibrary, + "-framework", + QuartzCore, + "-framework", + AssetsLibrary, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + AD2B8852198D631B0070367B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = armv7; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + FRAMEWORK_SEARCH_PATHS = ( + "~/Library/Developer/GStreamer/iPhone.sdk", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "GstPlay/GstPlay-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"~/Library/Developer/GStreamer/iPhone.sdk/GStreamer.framework/Headers\"", + ../lib, + ); + INFOPLIST_FILE = "GstPlay/GstPlay-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.1; + OTHER_LDFLAGS = ( + "-lresolv", + "-lstdc++", + "-framework", + CoreFoundation, + "-framework", + Foundation, + "-framework", + AVFoundation, + "-framework", + CoreMedia, + "-framework", + CoreVideo, + "-framework", + CoreAudio, + "-framework", + AudioToolbox, + "-framework", + OpenGLES, + "-framework", + AssetsLibrary, + "-framework", + QuartzCore, + "-framework", + AssetsLibrary, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AD2B8816198D631B0070367B /* Build configuration list for PBXProject "GstPlay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AD2B884E198D631B0070367B /* Debug */, + AD2B884F198D631B0070367B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AD2B8850198D631B0070367B /* Build configuration list for PBXNativeTarget "GstPlay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AD2B8851198D631B0070367B /* Debug */, + AD2B8852198D631B0070367B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AD2B8813198D631B0070367B /* Project object */; +} diff --git a/playback/player/ios/GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/playback/player/ios/GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..955a731746 --- /dev/null +++ b/playback/player/ios/GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/GstPlay.xcscheme b/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/GstPlay.xcscheme new file mode 100644 index 0000000000..c536703675 --- /dev/null +++ b/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/GstPlay.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/xcschememanagement.plist b/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..c980ecffbc --- /dev/null +++ b/playback/player/ios/GstPlay.xcodeproj/xcuserdata/slomo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + GstPlay.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + AD2B881A198D631B0070367B + + primary + + + AD2B883E198D631B0070367B + + primary + + + + + diff --git a/playback/player/ios/GstPlay/AppDelegate.h b/playback/player/ios/GstPlay/AppDelegate.h new file mode 100644 index 0000000000..767a22d9c7 --- /dev/null +++ b/playback/player/ios/GstPlay/AppDelegate.h @@ -0,0 +1,15 @@ +// +// AppDelegate.h +// GstPlay +// +// Created by Sebastian Dröge on 02/08/14. +// Copyright (c) 2014 Sebastian Dröge. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/playback/player/ios/GstPlay/AppDelegate.m b/playback/player/ios/GstPlay/AppDelegate.m new file mode 100644 index 0000000000..cb500b2233 --- /dev/null +++ b/playback/player/ios/GstPlay/AppDelegate.m @@ -0,0 +1,45 @@ +// +// AppDelegate.m +// GstPlay +// +// Created by Sebastian Dröge on 02/08/14. +// Copyright (c) 2014 Sebastian Dröge. All rights reserved. +// + +#import "AppDelegate.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application +{ + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application +{ + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application +{ + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/playback/player/ios/GstPlay/EaglUIVIew.h b/playback/player/ios/GstPlay/EaglUIVIew.h new file mode 100644 index 0000000000..ac4e79800b --- /dev/null +++ b/playback/player/ios/GstPlay/EaglUIVIew.h @@ -0,0 +1,11 @@ +#import +#import +#include +#include + +@interface EaglUIView : UIView +{ +} + +@end + diff --git a/playback/player/ios/GstPlay/EaglUIVIew.m b/playback/player/ios/GstPlay/EaglUIVIew.m new file mode 100644 index 0000000000..91cdd9dcf8 --- /dev/null +++ b/playback/player/ios/GstPlay/EaglUIVIew.m @@ -0,0 +1,13 @@ +#import "EaglUIVIew.h" + +#import + +@implementation EaglUIView + + ++ (Class) layerClass +{ + return [CAEAGLLayer class]; +} + +@end diff --git a/playback/player/ios/GstPlay/GstPlay-Info.plist b/playback/player/ios/GstPlay/GstPlay-Info.plist new file mode 100644 index 0000000000..180da25351 --- /dev/null +++ b/playback/player/ios/GstPlay/GstPlay-Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.freedesktop.gstreamer.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIMainStoryboardFile + MainStoryboard_iPhone + UIMainStoryboardFile~ipad + MainStoryboard_iPad + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/playback/player/ios/GstPlay/GstPlay-Prefix.pch b/playback/player/ios/GstPlay/GstPlay-Prefix.pch new file mode 100644 index 0000000000..743435c9be --- /dev/null +++ b/playback/player/ios/GstPlay/GstPlay-Prefix.pch @@ -0,0 +1,16 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iOS SDK 3.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/playback/player/ios/GstPlay/LibraryViewController.h b/playback/player/ios/GstPlay/LibraryViewController.h new file mode 100644 index 0000000000..6950b19842 --- /dev/null +++ b/playback/player/ios/GstPlay/LibraryViewController.h @@ -0,0 +1,12 @@ +#import + +@interface LibraryViewController : UITableViewController +{ + NSArray *libraryEntries; + NSArray *mediaEntries; + NSArray *onlineEntries; +} + +- (IBAction)refresh:(id)sender; + +@end diff --git a/playback/player/ios/GstPlay/LibraryViewController.m b/playback/player/ios/GstPlay/LibraryViewController.m new file mode 100644 index 0000000000..f1aead5648 --- /dev/null +++ b/playback/player/ios/GstPlay/LibraryViewController.m @@ -0,0 +1,165 @@ +#import "LibraryViewController.h" +#import "VideoViewController.h" +#import + +@interface LibraryViewController () + +@end + +@implementation LibraryViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + [super setTitle:@"Library"]; + [self refreshMediaItems]; +} + +- (IBAction)refresh:(id)sender +{ + [self refreshMediaItems]; + [self.tableView reloadData]; +} + +static NSString *CellIdentifier = @"CellIdentifier"; + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 3; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + switch (section) + { + case 0: return @"Photo library"; + case 1: return @"iTunes file sharing"; + default: return @"Online files"; + } +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + switch (section) { + case 0: + return [self->libraryEntries count]; + case 1: + return [self->mediaEntries count]; + case 2: + return [self->onlineEntries count]; + default: + return 0; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + // Configure Cell + UILabel *title = (UILabel *)[cell.contentView viewWithTag:10]; + UILabel *subtitle = (UILabel *)[cell.contentView viewWithTag:11]; + + switch (indexPath.section) { + case 0: subtitle.text = [self->libraryEntries objectAtIndex:indexPath.item]; + break; + case 1: subtitle.text = [self->mediaEntries objectAtIndex:indexPath.item]; + break; + case 2: subtitle.text = [self->onlineEntries objectAtIndex:indexPath.item]; + break; + default: + break; + } + + NSArray *components = [subtitle.text pathComponents]; + title.text = components.lastObject; + + return cell; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + return NO; +} + +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { + return NO; +} + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + if ([segue.identifier isEqualToString:@"playVideo"]) { + NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; + VideoViewController *destViewController = segue.destinationViewController; + UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath]; + UILabel *label = (UILabel *)[cell.contentView viewWithTag:10]; + destViewController.title = label.text; + label = (UILabel *)[cell.contentView viewWithTag:11]; + destViewController.uri = label.text; + } +} + +- (void)refreshMediaItems { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSAllDomainsMask, YES); + NSString *docsPath = [paths objectAtIndex:0]; + + /* Entries from the Photo Library */ + NSMutableArray *entries = [[NSMutableArray alloc] init]; + ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; + [library enumerateGroupsWithTypes:ALAssetsGroupAll + usingBlock:^(ALAssetsGroup *group, BOOL *stop) + { + if (group) { + [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) + { + if(result) { + [entries addObject:[NSString stringWithFormat:@"%@",[result valueForProperty:ALAssetPropertyAssetURL]]]; + *stop = NO; + + } + }]; + } else { + [self.tableView reloadData]; + } + } + failureBlock:^(NSError *error) + { + NSLog(@"ERROR"); + } + ]; + self->libraryEntries = entries; + + /* Retrieve entries from iTunes file sharing */ + entries = [[NSMutableArray alloc] init]; + for (NSString *e in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:docsPath error:nil]) + { + [entries addObject:[NSString stringWithFormat:@"file://%@/%@", docsPath, e]]; + } + self->mediaEntries = entries; + + /* Hardcoded list of Online media files */ + entries = [[NSMutableArray alloc] init]; + + // Big Buck Bunny + [entries addObject:@"http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi"]; + [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov"]; + [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg"]; + [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi"]; + + // Sintel + [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv"]; + [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.mp4"]; + [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.ogv"]; + [entries addObject:@"http://mirrorblender.top-ix.org/movies/sintel-1024-surround.mp4"]; + + // Tears of Steel + [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mkv"]; + [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mov"]; + [entries addObject:@"http://media.xiph.org/mango/tears_of_steel_1080p.webm"]; + + // Radio stations + [entries addObject:@"http://radio.hbr1.com:19800/trance.ogg"]; + [entries addObject:@"http://radio.hbr1.com:19800/tronic.aac"]; + + // Non-existing entries (to debug error reporting facilities) + [entries addObject:@"http://non-existing.org/Non_Existing_Server"]; + [entries addObject:@"http://docs.gstreamer.com/Non_Existing_File"]; + + self->onlineEntries = entries; +} + +@end diff --git a/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard b/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard new file mode 100644 index 0000000000..d1ea519820 --- /dev/null +++ b/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/ios/GstPlay/MainStoryboard_iPhone.storyboard b/playback/player/ios/GstPlay/MainStoryboard_iPhone.storyboard new file mode 100644 index 0000000000..8f43065744 --- /dev/null +++ b/playback/player/ios/GstPlay/MainStoryboard_iPhone.storyboard @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/ios/GstPlay/Ubuntu-R.ttf b/playback/player/ios/GstPlay/Ubuntu-R.ttf new file mode 100644 index 0000000000000000000000000000000000000000..45a038bade527cbbf30f197c5a116be3cfb2f0a3 GIT binary patch literal 353824 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 z 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 literal 0 HcmV?d00001 diff --git a/playback/player/ios/GstPlay/VideoViewController.h b/playback/player/ios/GstPlay/VideoViewController.h new file mode 100644 index 0000000000..97ee6e704b --- /dev/null +++ b/playback/player/ios/GstPlay/VideoViewController.h @@ -0,0 +1,23 @@ +#import + +@interface VideoViewController : UIViewController { + IBOutlet UIBarButtonItem *play_button; + IBOutlet UIBarButtonItem *pause_button; + IBOutlet UIView *video_view; + IBOutlet UIView *video_container_view; + IBOutlet NSLayoutConstraint *video_width_constraint; + IBOutlet NSLayoutConstraint *video_height_constraint; + IBOutlet UIToolbar *toolbar; + IBOutlet UITextField *time_label; + IBOutlet UISlider *time_slider; +} + +@property (retain,nonatomic) NSString *uri; + +-(IBAction) play:(id)sender; +-(IBAction) pause:(id)sender; +-(IBAction) sliderValueChanged:(id)sender; +-(IBAction) sliderTouchDown:(id)sender; +-(IBAction) sliderTouchUp:(id)sender; + +@end diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m new file mode 100644 index 0000000000..7863f90db1 --- /dev/null +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -0,0 +1,204 @@ +#import "VideoViewController.h" +#import +#import + +@interface VideoViewController () { + GstPlayer *player; + int media_width; /* Width of the clip */ + int media_height; /* height ofthe clip */ + Boolean dragging_slider; /* Whether the time slider is being dragged or not */ + Boolean is_local_media; /* Whether this clip is stored locally or is being streamed */ + Boolean is_playing_desired; /* Whether the user asked to go to PLAYING */ +} + +@end + +@implementation VideoViewController + +@synthesize uri; + +/* + * Private methods + */ + +/* The text widget acts as an slave for the seek bar, so it reflects what the seek bar shows, whether + * it is an actual pipeline position or the position the user is currently dragging to. */ +- (void) updateTimeWidget +{ + NSInteger position = time_slider.value / 1000; + NSInteger duration = time_slider.maximumValue / 1000; + NSString *position_txt = @" -- "; + NSString *duration_txt = @" -- "; + + if (duration > 0) { + NSUInteger hours = duration / (60 * 60); + NSUInteger minutes = (duration / 60) % 60; + NSUInteger seconds = duration % 60; + + duration_txt = [NSString stringWithFormat:@"%02u:%02u:%02u", hours, minutes, seconds]; + } + if (position > 0) { + NSUInteger hours = position / (60 * 60); + NSUInteger minutes = (position / 60) % 60; + NSUInteger seconds = position % 60; + + position_txt = [NSString stringWithFormat:@"%02u:%02u:%02u", hours, minutes, seconds]; + } + + NSString *text = [NSString stringWithFormat:@"%@ / %@", + position_txt, duration_txt]; + + time_label.text = text; +} + +/* + * Methods from UIViewController + */ + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + /* As soon as the GStreamer backend knows the real values, these ones will be replaced */ + media_width = 320; + media_height = 240; + + player = gst_player_new(FALSE); + g_object_set (player, "window-handle", video_view, NULL); + g_object_set (player, "uri", [uri UTF8String], NULL); + + gst_debug_set_threshold_for_name("gst-player", GST_LEVEL_TRACE); + + g_signal_connect (player, "position-updated", G_CALLBACK (position_updated), (__bridge gpointer) self); + g_signal_connect (player, "duration-changed", G_CALLBACK (duration_changed), (__bridge gpointer) self); + g_signal_connect (player, "video-dimensions-changed", G_CALLBACK (video_dimensions_changed), (__bridge gpointer) self); + + is_local_media = [uri hasPrefix:@"file://"]; + is_playing_desired = NO; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + if (player) + { + gst_object_unref (player); + } + [UIApplication sharedApplication].idleTimerDisabled = NO; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +/* Called when the Play button is pressed */ +-(IBAction) play:(id)sender +{ + gst_player_play (player); + is_playing_desired = YES; + [UIApplication sharedApplication].idleTimerDisabled = YES; +} + +/* Called when the Pause button is pressed */ +-(IBAction) pause:(id)sender +{ + gst_player_pause(player); + is_playing_desired = NO; + [UIApplication sharedApplication].idleTimerDisabled = NO; +} + +/* Called when the time slider position has changed, either because the user dragged it or + * we programmatically changed its position. dragging_slider tells us which one happened */ +- (IBAction)sliderValueChanged:(id)sender { + if (!dragging_slider) return; + // If this is a local file, allow scrub seeking, this is, seek as soon as the slider is moved. + if (is_local_media) + gst_player_seek (player, ((long)time_slider.value) * 1000000); + [self updateTimeWidget]; +} + +/* Called when the user starts to drag the time slider */ +- (IBAction)sliderTouchDown:(id)sender { + gst_player_pause (player); + dragging_slider = YES; +} + +/* Called when the user stops dragging the time slider */ +- (IBAction)sliderTouchUp:(id)sender { + dragging_slider = NO; + // If this is a remote file, scrub seeking is probably not going to work smoothly enough. + // Therefore, perform only the seek when the slider is released. + if (!is_local_media) + gst_player_seek (player, ((long)time_slider.value) * 1000000); + if (is_playing_desired) + gst_player_play (player); +} + +/* Called when the size of the main view has changed, so we can + * resize the sub-views in ways not allowed by storyboarding. */ +- (void)viewDidLayoutSubviews +{ + CGFloat view_width = video_container_view.bounds.size.width; + CGFloat view_height = video_container_view.bounds.size.height; + + CGFloat correct_height = view_width * media_height / media_width; + CGFloat correct_width = view_height * media_width / media_height; + + if (correct_height < view_height) { + video_height_constraint.constant = correct_height; + video_width_constraint.constant = view_width; + } else { + video_width_constraint.constant = correct_width; + video_height_constraint.constant = view_height; + } + + time_slider.frame = CGRectMake(time_slider.frame.origin.x, time_slider.frame.origin.y, toolbar.frame.size.width - time_slider.frame.origin.x - 8, time_slider.frame.size.height); +} + +static void video_dimensions_changed (GstPlayer * unused, gint width, gint height, VideoViewController * self) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self videoDimensionsChanged:width height:height]; + }); +} + +-(void) videoDimensionsChanged:(NSInteger)width height:(NSInteger)height +{ + media_width = width; + media_height = height; + [self viewDidLayoutSubviews]; + [video_view setNeedsLayout]; + [video_view layoutIfNeeded]; +} + +static void position_updated (GstPlayer * unused, GstClockTime position, VideoViewController *self) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self positionUpdated:(int) (position / 1000000)]; + }); +} + +-(void) positionUpdated:(NSInteger)position +{ + /* Ignore messages from the pipeline if the time sliders is being dragged */ + if (dragging_slider) return; + + time_slider.value = position; + [self updateTimeWidget]; +} + +static void duration_changed (GstPlayer * unused, GstClockTime duration, VideoViewController *self) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self durationChanged:(int) (duration / 1000000)]; + }); +} + +-(void) durationChanged:(NSInteger)duration +{ + time_slider.maximumValue = duration; + [self updateTimeWidget]; +} + +@end diff --git a/playback/player/ios/GstPlay/en.lproj/InfoPlist.strings b/playback/player/ios/GstPlay/en.lproj/InfoPlist.strings new file mode 100644 index 0000000000..477b28ff8f --- /dev/null +++ b/playback/player/ios/GstPlay/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/playback/player/ios/GstPlay/fonts.conf b/playback/player/ios/GstPlay/fonts.conf new file mode 100644 index 0000000000..6b780ea92e --- /dev/null +++ b/playback/player/ios/GstPlay/fonts.conf @@ -0,0 +1,126 @@ + + + + + + + +

+ + + + + + + + + mono + + + monospace + + + + + + + sans serif + + + sans-serif + + + + + + + sans + + + sans-serif + + + + + + + 0x0020 + 0x00A0 + 0x00AD + 0x034F + 0x0600 + 0x0601 + 0x0602 + 0x0603 + 0x06DD + 0x070F + 0x115F + 0x1160 + 0x1680 + 0x17B4 + 0x17B5 + 0x180E + 0x2000 + 0x2001 + 0x2002 + 0x2003 + 0x2004 + 0x2005 + 0x2006 + 0x2007 + 0x2008 + 0x2009 + 0x200A + 0x200B + 0x200C + 0x200D + 0x200E + 0x200F + 0x2028 + 0x2029 + 0x202A + 0x202B + 0x202C + 0x202D + 0x202E + 0x202F + 0x205F + 0x2060 + 0x2061 + 0x2062 + 0x2063 + 0x206A + 0x206B + 0x206C + 0x206D + 0x206E + 0x206F + 0x2800 + 0x3000 + 0x3164 + 0xFEFF + 0xFFA0 + 0xFFF9 + 0xFFFA + 0xFFFB + + + + 30 + + + + + diff --git a/playback/player/ios/GstPlay/gst_ios_init.h b/playback/player/ios/GstPlay/gst_ios_init.h new file mode 100644 index 0000000000..a880bf6363 --- /dev/null +++ b/playback/player/ios/GstPlay/gst_ios_init.h @@ -0,0 +1,39 @@ +#ifndef __GST_IOS_INIT_H__ +#define __GST_IOS_INIT_H__ + +#include + +G_BEGIN_DECLS + +#define GST_G_IO_MODULE_DECLARE(name) \ +extern void G_PASTE(g_io_module_, G_PASTE(name, _load_static)) (void) + +#define GST_G_IO_MODULE_LOAD(name) \ +G_PASTE(g_io_module_, G_PASTE(name, _load_static)) () + +/* Uncomment each line to enable the plugin categories that your application needs. + * You can also enable individual plugins. See gst_ios_init.c to see their names + */ + +#define GST_IOS_PLUGINS_CORE +//#define GST_IOS_PLUGINS_CAPTURE +#define GST_IOS_PLUGINS_CODECS_RESTRICTED +//#define GST_IOS_PLUGINS_ENCODING +#define GST_IOS_PLUGINS_CODECS_GPL +#define GST_IOS_PLUGINS_NET_RESTRICTED +#define GST_IOS_PLUGINS_SYS +#define GST_IOS_PLUGINS_VIS +#define GST_IOS_PLUGINS_PLAYBACK +#define GST_IOS_PLUGINS_EFFECTS +#define GST_IOS_PLUGINS_CODECS +#define GST_IOS_PLUGINS_NET +//#define GST_IOS_PLUGINS_EDITING + + +#define GST_IOS_GIO_MODULE_GNUTLS + +void gst_ios_init (); + +G_END_DECLS + +#endif diff --git a/playback/player/ios/GstPlay/gst_ios_init.m b/playback/player/ios/GstPlay/gst_ios_init.m new file mode 100644 index 0000000000..403f9f9c17 --- /dev/null +++ b/playback/player/ios/GstPlay/gst_ios_init.m @@ -0,0 +1,988 @@ +#include "gst_ios_init.h" + +#if defined(GST_IOS_PLUGIN_COREELEMENTS) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(coreelements); +#endif +#if defined(GST_IOS_PLUGIN_ADDER) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(adder); +#endif +#if defined(GST_IOS_PLUGIN_APP) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(app); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOCONVERT) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(audioconvert); +#endif +#if defined(GST_IOS_PLUGIN_AUDIORATE) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(audiorate); +#endif +#if defined(GST_IOS_PLUGIN_AUDIORESAMPLE) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(audioresample); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOTESTSRC) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(audiotestsrc); +#endif +#if defined(GST_IOS_PLUGIN_GIO) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(gio); +#endif +#if defined(GST_IOS_PLUGIN_PANGO) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(pango); +#endif +#if defined(GST_IOS_PLUGIN_TYPEFINDFUNCTIONS) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(typefindfunctions); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOCONVERT) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(videoconvert); +#endif +#if defined(GST_IOS_PLUGIN_VIDEORATE) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(videorate); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOSCALE) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(videoscale); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOTESTSRC) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(videotestsrc); +#endif +#if defined(GST_IOS_PLUGIN_VOLUME) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(volume); +#endif +#if defined(GST_IOS_PLUGIN_AUTODETECT) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(autodetect); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOFILTER) || defined(GST_IOS_PLUGINS_CORE) +GST_PLUGIN_STATIC_DECLARE(videofilter); +#endif +#if defined(GST_IOS_PLUGIN_CAMERABIN) || defined(GST_IOS_PLUGINS_CAPTURE) +GST_PLUGIN_STATIC_DECLARE(camerabin); +#endif +#if defined(GST_IOS_PLUGIN_ASFMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(asfmux); +#endif +#if defined(GST_IOS_PLUGIN_DTSDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(dtsdec); +#endif +#if defined(GST_IOS_PLUGIN_FAAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(faad); +#endif +#if defined(GST_IOS_PLUGIN_MPEGPSDEMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpegpsdemux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGPSMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpegpsmux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGTSDEMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpegtsdemux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGTSMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpegtsmux); +#endif +#if defined(GST_IOS_PLUGIN_VOAACENC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(voaacenc); +#endif +#if defined(GST_IOS_PLUGIN_A52DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(a52dec); +#endif +#if defined(GST_IOS_PLUGIN_AMRNB) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(amrnb); +#endif +#if defined(GST_IOS_PLUGIN_AMRWBDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(amrwbdec); +#endif +#if defined(GST_IOS_PLUGIN_ASF) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(asf); +#endif +#if defined(GST_IOS_PLUGIN_DVDSUB) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(dvdsub); +#endif +#if defined(GST_IOS_PLUGIN_DVDLPCMDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(dvdlpcmdec); +#endif +#if defined(GST_IOS_PLUGIN_MAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mad); +#endif +#if defined(GST_IOS_PLUGIN_MPEG2DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpeg2dec); +#endif +#if defined(GST_IOS_PLUGIN_XINGMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(xingmux); +#endif +#if defined(GST_IOS_PLUGIN_REALMEDIA) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(realmedia); +#endif +#if defined(GST_IOS_PLUGIN_X264) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(x264); +#endif +#if defined(GST_IOS_PLUGIN_LIBAV) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(libav); +#endif +#if defined(GST_IOS_PLUGIN_ENCODING) || defined(GST_IOS_PLUGINS_ENCODING) +GST_PLUGIN_STATIC_DECLARE(encoding); +#endif +#if defined(GST_IOS_PLUGIN_ASSRENDER) || defined(GST_IOS_PLUGINS_CODECS_GPL) +GST_PLUGIN_STATIC_DECLARE(assrender); +#endif +#if defined(GST_IOS_PLUGIN_MMS) || defined(GST_IOS_PLUGINS_NET_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mms); +#endif +#if defined(GST_IOS_PLUGIN_RTMP) || defined(GST_IOS_PLUGINS_NET_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(rtmp); +#endif +#if defined(GST_IOS_PLUGIN_OSXAUDIO) || defined(GST_IOS_PLUGINS_SYS) +GST_PLUGIN_STATIC_DECLARE(osxaudio); +#endif +#if defined(GST_IOS_PLUGIN_APPLEMEDIA) || defined(GST_IOS_PLUGINS_SYS) +GST_PLUGIN_STATIC_DECLARE(applemedia); +#endif +#if defined(GST_IOS_PLUGIN_SHM) || defined(GST_IOS_PLUGINS_SYS) +GST_PLUGIN_STATIC_DECLARE(shm); +#endif +#if defined(GST_IOS_PLUGIN_OPENGL) || defined(GST_IOS_PLUGINS_SYS) +GST_PLUGIN_STATIC_DECLARE(opengl); +#endif +#if defined(GST_IOS_PLUGIN_LIBVISUAL) || defined(GST_IOS_PLUGINS_VIS) +GST_PLUGIN_STATIC_DECLARE(libvisual); +#endif +#if defined(GST_IOS_PLUGIN_GOOM) || defined(GST_IOS_PLUGINS_VIS) +GST_PLUGIN_STATIC_DECLARE(goom); +#endif +#if defined(GST_IOS_PLUGIN_GOOM2K1) || defined(GST_IOS_PLUGINS_VIS) +GST_PLUGIN_STATIC_DECLARE(goom2k1); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOVISUALIZERS) || defined(GST_IOS_PLUGINS_VIS) +GST_PLUGIN_STATIC_DECLARE(audiovisualizers); +#endif +#if defined(GST_IOS_PLUGIN_PLAYBACK) || defined(GST_IOS_PLUGINS_PLAYBACK) +GST_PLUGIN_STATIC_DECLARE(playback); +#endif +#if defined(GST_IOS_PLUGIN_ALPHA) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(alpha); +#endif +#if defined(GST_IOS_PLUGIN_ALPHACOLOR) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(alphacolor); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOFX) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(audiofx); +#endif +#if defined(GST_IOS_PLUGIN_CAIRO) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(cairo); +#endif +#if defined(GST_IOS_PLUGIN_CUTTER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(cutter); +#endif +#if defined(GST_IOS_PLUGIN_DEBUG) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(debug); +#endif +#if defined(GST_IOS_PLUGIN_DEINTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(deinterlace); +#endif +#if defined(GST_IOS_PLUGIN_DTMF) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(dtmf); +#endif +#if defined(GST_IOS_PLUGIN_EFFECTV) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(effectv); +#endif +#if defined(GST_IOS_PLUGIN_EQUALIZER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(equalizer); +#endif +#if defined(GST_IOS_PLUGIN_GDKPIXBUF) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(gdkpixbuf); +#endif +#if defined(GST_IOS_PLUGIN_IMAGEFREEZE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(imagefreeze); +#endif +#if defined(GST_IOS_PLUGIN_INTERLEAVE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(interleave); +#endif +#if defined(GST_IOS_PLUGIN_LEVEL) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(level); +#endif +#if defined(GST_IOS_PLUGIN_MULTIFILE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(multifile); +#endif +#if defined(GST_IOS_PLUGIN_REPLAYGAIN) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(replaygain); +#endif +#if defined(GST_IOS_PLUGIN_SHAPEWIPE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(shapewipe); +#endif +#if defined(GST_IOS_PLUGIN_SMPTE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(smpte); +#endif +#if defined(GST_IOS_PLUGIN_SPECTRUM) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(spectrum); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOBOX) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(videobox); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOCROP) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(videocrop); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOMIXER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(videomixer); +#endif +#if defined(GST_IOS_PLUGIN_ACCURIP) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(accurip); +#endif +#if defined(GST_IOS_PLUGIN_AIFF) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(aiff); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOFXBAD) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(audiofxbad); +#endif +#if defined(GST_IOS_PLUGIN_AUTOCONVERT) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(autoconvert); +#endif +#if defined(GST_IOS_PLUGIN_BAYER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(bayer); +#endif +#if defined(GST_IOS_PLUGIN_COLOREFFECTS) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(coloreffects); +#endif +#if defined(GST_IOS_PLUGIN_DEBUGUTILSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(debugutilsbad); +#endif +#if defined(GST_IOS_PLUGIN_FIELDANALYSIS) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(fieldanalysis); +#endif +#if defined(GST_IOS_PLUGIN_FREEVERB) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(freeverb); +#endif +#if defined(GST_IOS_PLUGIN_FREI0R) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(frei0r); +#endif +#if defined(GST_IOS_PLUGIN_GAUDIEFFECTS) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(gaudieffects); +#endif +#if defined(GST_IOS_PLUGIN_GEOMETRICTRANSFORM) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(geometrictransform); +#endif +#if defined(GST_IOS_PLUGIN_INTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(interlace); +#endif +#if defined(GST_IOS_PLUGIN_IVTC) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(ivtc); +#endif +#if defined(GST_IOS_PLUGIN_LIVEADDER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(liveadder); +#endif +#if defined(GST_IOS_PLUGIN_RAWPARSE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(rawparse); +#endif +#if defined(GST_IOS_PLUGIN_REMOVESILENCE) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(removesilence); +#endif +#if defined(GST_IOS_PLUGIN_SEGMENTCLIP) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(segmentclip); +#endif +#if defined(GST_IOS_PLUGIN_SMOOTH) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(smooth); +#endif +#if defined(GST_IOS_PLUGIN_SPEED) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(speed); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOFILTERSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(videofiltersbad); +#endif +#if defined(GST_IOS_PLUGIN_SUBPARSE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(subparse); +#endif +#if defined(GST_IOS_PLUGIN_OGG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(ogg); +#endif +#if defined(GST_IOS_PLUGIN_THEORA) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(theora); +#endif +#if defined(GST_IOS_PLUGIN_VORBIS) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(vorbis); +#endif +#if defined(GST_IOS_PLUGIN_IVORBISDEC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(ivorbisdec); +#endif +#if defined(GST_IOS_PLUGIN_ALAW) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(alaw); +#endif +#if defined(GST_IOS_PLUGIN_APETAG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(apetag); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOPARSERS) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(audioparsers); +#endif +#if defined(GST_IOS_PLUGIN_AUPARSE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(auparse); +#endif +#if defined(GST_IOS_PLUGIN_AVI) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(avi); +#endif +#if defined(GST_IOS_PLUGIN_DV) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(dv); +#endif +#if defined(GST_IOS_PLUGIN_FLAC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(flac); +#endif +#if defined(GST_IOS_PLUGIN_FLV) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(flv); +#endif +#if defined(GST_IOS_PLUGIN_FLXDEC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(flxdec); +#endif +#if defined(GST_IOS_PLUGIN_ICYDEMUX) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(icydemux); +#endif +#if defined(GST_IOS_PLUGIN_ID3DEMUX) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(id3demux); +#endif +#if defined(GST_IOS_PLUGIN_ISOMP4) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(isomp4); +#endif +#if defined(GST_IOS_PLUGIN_JPEG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(jpeg); +#endif +#if defined(GST_IOS_PLUGIN_MATROSKA) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(matroska); +#endif +#if defined(GST_IOS_PLUGIN_MULAW) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(mulaw); +#endif +#if defined(GST_IOS_PLUGIN_MULTIPART) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(multipart); +#endif +#if defined(GST_IOS_PLUGIN_PNG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(png); +#endif +#if defined(GST_IOS_PLUGIN_SPEEX) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(speex); +#endif +#if defined(GST_IOS_PLUGIN_TAGLIB) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(taglib); +#endif +#if defined(GST_IOS_PLUGIN_VPX) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(vpx); +#endif +#if defined(GST_IOS_PLUGIN_WAVENC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(wavenc); +#endif +#if defined(GST_IOS_PLUGIN_WAVPACK) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(wavpack); +#endif +#if defined(GST_IOS_PLUGIN_WAVPARSE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(wavparse); +#endif +#if defined(GST_IOS_PLUGIN_Y4MENC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(y4menc); +#endif +#if defined(GST_IOS_PLUGIN_ADPCMDEC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(adpcmdec); +#endif +#if defined(GST_IOS_PLUGIN_ADPCMENC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(adpcmenc); +#endif +#if defined(GST_IOS_PLUGIN_DASHDEMUX) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(dashdemux); +#endif +#if defined(GST_IOS_PLUGIN_DVBSUBOVERLAY) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(dvbsuboverlay); +#endif +#if defined(GST_IOS_PLUGIN_DVDSPU) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(dvdspu); +#endif +#if defined(GST_IOS_PLUGIN_FRAGMENTED) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(fragmented); +#endif +#if defined(GST_IOS_PLUGIN_ID3TAG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(id3tag); +#endif +#if defined(GST_IOS_PLUGIN_KATE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(kate); +#endif +#if defined(GST_IOS_PLUGIN_MIDI) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(midi); +#endif +#if defined(GST_IOS_PLUGIN_MXF) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(mxf); +#endif +#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(opus); +#endif +#if defined(GST_IOS_PLUGIN_PCAPPARSE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(pcapparse); +#endif +#if defined(GST_IOS_PLUGIN_PNM) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(pnm); +#endif +#if defined(GST_IOS_PLUGIN_RFBSRC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(rfbsrc); +#endif +#if defined(GST_IOS_PLUGIN_SCHRO) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(schro); +#endif +#if defined(GST_IOS_PLUGIN_GSTSIREN) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(gstsiren); +#endif +#if defined(GST_IOS_PLUGIN_SMOOTHSTREAMING) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(smoothstreaming); +#endif +#if defined(GST_IOS_PLUGIN_SUBENC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(subenc); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOPARSERSBAD) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(videoparsersbad); +#endif +#if defined(GST_IOS_PLUGIN_Y4MDEC) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(y4mdec); +#endif +#if defined(GST_IOS_PLUGIN_JPEGFORMAT) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(jpegformat); +#endif +#if defined(GST_IOS_PLUGIN_GDP) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(gdp); +#endif +#if defined(GST_IOS_PLUGIN_RSVG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(rsvg); +#endif +#if defined(GST_IOS_PLUGIN_TCP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(tcp); +#endif +#if defined(GST_IOS_PLUGIN_RTSP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(rtsp); +#endif +#if defined(GST_IOS_PLUGIN_RTP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(rtp); +#endif +#if defined(GST_IOS_PLUGIN_RTPMANAGER) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(rtpmanager); +#endif +#if defined(GST_IOS_PLUGIN_SOUP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(soup); +#endif +#if defined(GST_IOS_PLUGIN_UDP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(udp); +#endif +#if defined(GST_IOS_PLUGIN_DATAURISRC) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(dataurisrc); +#endif +#if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(sdp); +#endif +#if defined(GST_IOS_PLUGIN_GNONLIN) || defined(GST_IOS_PLUGINS_EDITING) +GST_PLUGIN_STATIC_DECLARE(gnonlin); +#endif + +#if defined(GST_IOS_GIO_MODULE_GNUTLS) + #include + GST_G_IO_MODULE_DECLARE(gnutls); +#endif + +void +gst_ios_init (void) +{ + GstPluginFeature *plugin; + GstRegistry *reg; + NSString *resources = [[NSBundle mainBundle] resourcePath]; + NSString *tmp = NSTemporaryDirectory(); + NSString *cache = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches"]; + NSString *docs = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; + + const gchar *resources_dir = [resources UTF8String]; + const gchar *tmp_dir = [tmp UTF8String]; + const gchar *cache_dir = [cache UTF8String]; + const gchar *docs_dir = [docs UTF8String]; + gchar *ca_certificates; + + g_setenv ("TMP", tmp_dir, TRUE); + g_setenv ("TEMP", tmp_dir, TRUE); + g_setenv ("TMPDIR", tmp_dir, TRUE); + g_setenv ("XDG_RUNTIME_DIR", resources_dir, TRUE); + g_setenv ("XDG_CACHE_HOME", cache_dir, TRUE); + + g_setenv ("HOME", docs_dir, TRUE); + g_setenv ("XDG_DATA_DIRS", resources_dir, TRUE); + g_setenv ("XDG_CONFIG_DIRS", resources_dir, TRUE); + g_setenv ("XDG_CONFIG_HOME", cache_dir, TRUE); + g_setenv ("XDG_DATA_HOME", resources_dir, TRUE); + g_setenv ("FONTCONFIG_PATH", resources_dir, TRUE); + + ca_certificates = g_build_filename (resources_dir, "ssl", "certs", "ca-certifcates.crt", NULL); + g_setenv ("CA_CERTIFICATES", ca_certificates, TRUE); + g_free (ca_certificates); + + gst_init (NULL, NULL); + + #if defined(GST_IOS_PLUGIN_COREELEMENTS) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(coreelements); +#endif +#if defined(GST_IOS_PLUGIN_ADDER) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(adder); +#endif +#if defined(GST_IOS_PLUGIN_APP) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(app); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOCONVERT) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(audioconvert); +#endif +#if defined(GST_IOS_PLUGIN_AUDIORATE) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(audiorate); +#endif +#if defined(GST_IOS_PLUGIN_AUDIORESAMPLE) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(audioresample); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOTESTSRC) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(audiotestsrc); +#endif +#if defined(GST_IOS_PLUGIN_GIO) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(gio); +#endif +#if defined(GST_IOS_PLUGIN_PANGO) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(pango); +#endif +#if defined(GST_IOS_PLUGIN_TYPEFINDFUNCTIONS) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(typefindfunctions); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOCONVERT) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(videoconvert); +#endif +#if defined(GST_IOS_PLUGIN_VIDEORATE) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(videorate); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOSCALE) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(videoscale); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOTESTSRC) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(videotestsrc); +#endif +#if defined(GST_IOS_PLUGIN_VOLUME) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(volume); +#endif +#if defined(GST_IOS_PLUGIN_AUTODETECT) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(autodetect); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOFILTER) || defined(GST_IOS_PLUGINS_CORE) + GST_PLUGIN_STATIC_REGISTER(videofilter); +#endif +#if defined(GST_IOS_PLUGIN_CAMERABIN) || defined(GST_IOS_PLUGINS_CAPTURE) + GST_PLUGIN_STATIC_REGISTER(camerabin); +#endif +#if defined(GST_IOS_PLUGIN_ASFMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(asfmux); +#endif +#if defined(GST_IOS_PLUGIN_DTSDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(dtsdec); +#endif +#if defined(GST_IOS_PLUGIN_FAAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(faad); +#endif +#if defined(GST_IOS_PLUGIN_MPEGPSDEMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpegpsdemux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGPSMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpegpsmux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGTSDEMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpegtsdemux); +#endif +#if defined(GST_IOS_PLUGIN_MPEGTSMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpegtsmux); +#endif +#if defined(GST_IOS_PLUGIN_VOAACENC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(voaacenc); +#endif +#if defined(GST_IOS_PLUGIN_A52DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(a52dec); +#endif +#if defined(GST_IOS_PLUGIN_AMRNB) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(amrnb); +#endif +#if defined(GST_IOS_PLUGIN_AMRWBDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(amrwbdec); +#endif +#if defined(GST_IOS_PLUGIN_ASF) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(asf); +#endif +#if defined(GST_IOS_PLUGIN_DVDSUB) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(dvdsub); +#endif +#if defined(GST_IOS_PLUGIN_DVDLPCMDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(dvdlpcmdec); +#endif +#if defined(GST_IOS_PLUGIN_MAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mad); +#endif +#if defined(GST_IOS_PLUGIN_MPEG2DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpeg2dec); +#endif +#if defined(GST_IOS_PLUGIN_XINGMUX) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(xingmux); +#endif +#if defined(GST_IOS_PLUGIN_REALMEDIA) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(realmedia); +#endif +#if defined(GST_IOS_PLUGIN_X264) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(x264); +#endif +#if defined(GST_IOS_PLUGIN_LIBAV) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(libav); +#endif +#if defined(GST_IOS_PLUGIN_ENCODING) || defined(GST_IOS_PLUGINS_ENCODING) + GST_PLUGIN_STATIC_REGISTER(encoding); +#endif +#if defined(GST_IOS_PLUGIN_ASSRENDER) || defined(GST_IOS_PLUGINS_CODECS_GPL) + GST_PLUGIN_STATIC_REGISTER(assrender); +#endif +#if defined(GST_IOS_PLUGIN_MMS) || defined(GST_IOS_PLUGINS_NET_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mms); +#endif +#if defined(GST_IOS_PLUGIN_RTMP) || defined(GST_IOS_PLUGINS_NET_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(rtmp); +#endif +#if defined(GST_IOS_PLUGIN_OSXAUDIO) || defined(GST_IOS_PLUGINS_SYS) + GST_PLUGIN_STATIC_REGISTER(osxaudio); +#endif +#if defined(GST_IOS_PLUGIN_APPLEMEDIA) || defined(GST_IOS_PLUGINS_SYS) + GST_PLUGIN_STATIC_REGISTER(applemedia); +#endif +#if defined(GST_IOS_PLUGIN_SHM) || defined(GST_IOS_PLUGINS_SYS) + GST_PLUGIN_STATIC_REGISTER(shm); +#endif +#if defined(GST_IOS_PLUGIN_OPENGL) || defined(GST_IOS_PLUGINS_SYS) + GST_PLUGIN_STATIC_REGISTER(opengl); +#endif +#if defined(GST_IOS_PLUGIN_LIBVISUAL) || defined(GST_IOS_PLUGINS_VIS) + GST_PLUGIN_STATIC_REGISTER(libvisual); +#endif +#if defined(GST_IOS_PLUGIN_GOOM) || defined(GST_IOS_PLUGINS_VIS) + GST_PLUGIN_STATIC_REGISTER(goom); +#endif +#if defined(GST_IOS_PLUGIN_GOOM2K1) || defined(GST_IOS_PLUGINS_VIS) + GST_PLUGIN_STATIC_REGISTER(goom2k1); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOVISUALIZERS) || defined(GST_IOS_PLUGINS_VIS) + GST_PLUGIN_STATIC_REGISTER(audiovisualizers); +#endif +#if defined(GST_IOS_PLUGIN_PLAYBACK) || defined(GST_IOS_PLUGINS_PLAYBACK) + GST_PLUGIN_STATIC_REGISTER(playback); +#endif +#if defined(GST_IOS_PLUGIN_ALPHA) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(alpha); +#endif +#if defined(GST_IOS_PLUGIN_ALPHACOLOR) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(alphacolor); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOFX) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(audiofx); +#endif +#if defined(GST_IOS_PLUGIN_CAIRO) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(cairo); +#endif +#if defined(GST_IOS_PLUGIN_CUTTER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(cutter); +#endif +#if defined(GST_IOS_PLUGIN_DEBUG) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(debug); +#endif +#if defined(GST_IOS_PLUGIN_DEINTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(deinterlace); +#endif +#if defined(GST_IOS_PLUGIN_DTMF) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(dtmf); +#endif +#if defined(GST_IOS_PLUGIN_EFFECTV) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(effectv); +#endif +#if defined(GST_IOS_PLUGIN_EQUALIZER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(equalizer); +#endif +#if defined(GST_IOS_PLUGIN_GDKPIXBUF) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(gdkpixbuf); +#endif +#if defined(GST_IOS_PLUGIN_IMAGEFREEZE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(imagefreeze); +#endif +#if defined(GST_IOS_PLUGIN_INTERLEAVE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(interleave); +#endif +#if defined(GST_IOS_PLUGIN_LEVEL) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(level); +#endif +#if defined(GST_IOS_PLUGIN_MULTIFILE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(multifile); +#endif +#if defined(GST_IOS_PLUGIN_REPLAYGAIN) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(replaygain); +#endif +#if defined(GST_IOS_PLUGIN_SHAPEWIPE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(shapewipe); +#endif +#if defined(GST_IOS_PLUGIN_SMPTE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(smpte); +#endif +#if defined(GST_IOS_PLUGIN_SPECTRUM) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(spectrum); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOBOX) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(videobox); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOCROP) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(videocrop); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOMIXER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(videomixer); +#endif +#if defined(GST_IOS_PLUGIN_ACCURIP) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(accurip); +#endif +#if defined(GST_IOS_PLUGIN_AIFF) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(aiff); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOFXBAD) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(audiofxbad); +#endif +#if defined(GST_IOS_PLUGIN_AUTOCONVERT) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(autoconvert); +#endif +#if defined(GST_IOS_PLUGIN_BAYER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(bayer); +#endif +#if defined(GST_IOS_PLUGIN_COLOREFFECTS) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(coloreffects); +#endif +#if defined(GST_IOS_PLUGIN_DEBUGUTILSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(debugutilsbad); +#endif +#if defined(GST_IOS_PLUGIN_FIELDANALYSIS) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(fieldanalysis); +#endif +#if defined(GST_IOS_PLUGIN_FREEVERB) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(freeverb); +#endif +#if defined(GST_IOS_PLUGIN_FREI0R) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(frei0r); +#endif +#if defined(GST_IOS_PLUGIN_GAUDIEFFECTS) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(gaudieffects); +#endif +#if defined(GST_IOS_PLUGIN_GEOMETRICTRANSFORM) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(geometrictransform); +#endif +#if defined(GST_IOS_PLUGIN_INTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(interlace); +#endif +#if defined(GST_IOS_PLUGIN_IVTC) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(ivtc); +#endif +#if defined(GST_IOS_PLUGIN_LIVEADDER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(liveadder); +#endif +#if defined(GST_IOS_PLUGIN_RAWPARSE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(rawparse); +#endif +#if defined(GST_IOS_PLUGIN_REMOVESILENCE) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(removesilence); +#endif +#if defined(GST_IOS_PLUGIN_SEGMENTCLIP) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(segmentclip); +#endif +#if defined(GST_IOS_PLUGIN_SMOOTH) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(smooth); +#endif +#if defined(GST_IOS_PLUGIN_SPEED) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(speed); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOFILTERSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(videofiltersbad); +#endif +#if defined(GST_IOS_PLUGIN_SUBPARSE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(subparse); +#endif +#if defined(GST_IOS_PLUGIN_OGG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(ogg); +#endif +#if defined(GST_IOS_PLUGIN_THEORA) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(theora); +#endif +#if defined(GST_IOS_PLUGIN_VORBIS) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(vorbis); +#endif +#if defined(GST_IOS_PLUGIN_IVORBISDEC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(ivorbisdec); +#endif +#if defined(GST_IOS_PLUGIN_ALAW) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(alaw); +#endif +#if defined(GST_IOS_PLUGIN_APETAG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(apetag); +#endif +#if defined(GST_IOS_PLUGIN_AUDIOPARSERS) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(audioparsers); +#endif +#if defined(GST_IOS_PLUGIN_AUPARSE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(auparse); +#endif +#if defined(GST_IOS_PLUGIN_AVI) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(avi); +#endif +#if defined(GST_IOS_PLUGIN_DV) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(dv); +#endif +#if defined(GST_IOS_PLUGIN_FLAC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(flac); +#endif +#if defined(GST_IOS_PLUGIN_FLV) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(flv); +#endif +#if defined(GST_IOS_PLUGIN_FLXDEC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(flxdec); +#endif +#if defined(GST_IOS_PLUGIN_ICYDEMUX) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(icydemux); +#endif +#if defined(GST_IOS_PLUGIN_ID3DEMUX) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(id3demux); +#endif +#if defined(GST_IOS_PLUGIN_ISOMP4) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(isomp4); +#endif +#if defined(GST_IOS_PLUGIN_JPEG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(jpeg); +#endif +#if defined(GST_IOS_PLUGIN_MATROSKA) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(matroska); +#endif +#if defined(GST_IOS_PLUGIN_MULAW) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(mulaw); +#endif +#if defined(GST_IOS_PLUGIN_MULTIPART) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(multipart); +#endif +#if defined(GST_IOS_PLUGIN_PNG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(png); +#endif +#if defined(GST_IOS_PLUGIN_SPEEX) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(speex); +#endif +#if defined(GST_IOS_PLUGIN_TAGLIB) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(taglib); +#endif +#if defined(GST_IOS_PLUGIN_VPX) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(vpx); +#endif +#if defined(GST_IOS_PLUGIN_WAVENC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(wavenc); +#endif +#if defined(GST_IOS_PLUGIN_WAVPACK) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(wavpack); +#endif +#if defined(GST_IOS_PLUGIN_WAVPARSE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(wavparse); +#endif +#if defined(GST_IOS_PLUGIN_Y4MENC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(y4menc); +#endif +#if defined(GST_IOS_PLUGIN_ADPCMDEC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(adpcmdec); +#endif +#if defined(GST_IOS_PLUGIN_ADPCMENC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(adpcmenc); +#endif +#if defined(GST_IOS_PLUGIN_DASHDEMUX) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(dashdemux); +#endif +#if defined(GST_IOS_PLUGIN_DVBSUBOVERLAY) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(dvbsuboverlay); +#endif +#if defined(GST_IOS_PLUGIN_DVDSPU) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(dvdspu); +#endif +#if defined(GST_IOS_PLUGIN_FRAGMENTED) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(fragmented); +#endif +#if defined(GST_IOS_PLUGIN_ID3TAG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(id3tag); +#endif +#if defined(GST_IOS_PLUGIN_KATE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(kate); +#endif +#if defined(GST_IOS_PLUGIN_MIDI) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(midi); +#endif +#if defined(GST_IOS_PLUGIN_MXF) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(mxf); +#endif +#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(opus); +#endif +#if defined(GST_IOS_PLUGIN_PCAPPARSE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(pcapparse); +#endif +#if defined(GST_IOS_PLUGIN_PNM) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(pnm); +#endif +#if defined(GST_IOS_PLUGIN_RFBSRC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(rfbsrc); +#endif +#if defined(GST_IOS_PLUGIN_SCHRO) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(schro); +#endif +#if defined(GST_IOS_PLUGIN_GSTSIREN) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(gstsiren); +#endif +#if defined(GST_IOS_PLUGIN_SMOOTHSTREAMING) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(smoothstreaming); +#endif +#if defined(GST_IOS_PLUGIN_SUBENC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(subenc); +#endif +#if defined(GST_IOS_PLUGIN_VIDEOPARSERSBAD) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(videoparsersbad); +#endif +#if defined(GST_IOS_PLUGIN_Y4MDEC) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(y4mdec); +#endif +#if defined(GST_IOS_PLUGIN_JPEGFORMAT) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(jpegformat); +#endif +#if defined(GST_IOS_PLUGIN_GDP) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(gdp); +#endif +#if defined(GST_IOS_PLUGIN_RSVG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(rsvg); +#endif +#if defined(GST_IOS_PLUGIN_TCP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(tcp); +#endif +#if defined(GST_IOS_PLUGIN_RTSP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(rtsp); +#endif +#if defined(GST_IOS_PLUGIN_RTP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(rtp); +#endif +#if defined(GST_IOS_PLUGIN_RTPMANAGER) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(rtpmanager); +#endif +#if defined(GST_IOS_PLUGIN_SOUP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(soup); +#endif +#if defined(GST_IOS_PLUGIN_UDP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(udp); +#endif +#if defined(GST_IOS_PLUGIN_DATAURISRC) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(dataurisrc); +#endif +#if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(sdp); +#endif +#if defined(GST_IOS_PLUGIN_GNONLIN) || defined(GST_IOS_PLUGINS_EDITING) + GST_PLUGIN_STATIC_REGISTER(gnonlin); +#endif + +#if defined(GST_IOS_GIO_MODULE_GNUTLS) + GST_G_IO_MODULE_LOAD(gnutls); +#endif + + /* Lower the ranks of filesrc and giosrc so iosavassetsrc is + * tried first in gst_element_make_from_uri() for file:// */ + reg = gst_registry_get(); + plugin = gst_registry_lookup_feature(reg, "filesrc"); + if (plugin) + gst_plugin_feature_set_rank(plugin, GST_RANK_SECONDARY); + plugin = gst_registry_lookup_feature(reg, "giosrc"); + if (plugin) + gst_plugin_feature_set_rank(plugin, GST_RANK_SECONDARY-1); +} diff --git a/playback/player/ios/GstPlay/main.m b/playback/player/ios/GstPlay/main.m new file mode 100644 index 0000000000..6cf4e0f612 --- /dev/null +++ b/playback/player/ios/GstPlay/main.m @@ -0,0 +1,12 @@ +#import + +#import "AppDelegate.h" +#include "gst_ios_init.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + gst_ios_init(); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} From 090c7665629026ddb3429ed18c696c64216fb587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 3 Aug 2014 12:35:39 +0200 Subject: [PATCH 020/412] playback/player: Add initial build system with docs and tests and magic --- playback/player/android/Makefile.am | 10 ++++++++++ playback/player/gst-play/Makefile | 21 --------------------- playback/player/gst-play/Makefile.am | 10 ++++++++++ playback/player/gtk/Makefile | 19 ------------------- playback/player/gtk/Makefile.am | 10 ++++++++++ playback/player/ios/Makefile.am | 22 ++++++++++++++++++++++ 6 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 playback/player/android/Makefile.am delete mode 100644 playback/player/gst-play/Makefile create mode 100644 playback/player/gst-play/Makefile.am delete mode 100644 playback/player/gtk/Makefile create mode 100644 playback/player/gtk/Makefile.am create mode 100644 playback/player/ios/Makefile.am diff --git a/playback/player/android/Makefile.am b/playback/player/android/Makefile.am new file mode 100644 index 0000000000..2664abbab9 --- /dev/null +++ b/playback/player/android/Makefile.am @@ -0,0 +1,10 @@ +EXTRA_DIST = \ + AndroidManifest.xml \ + jni/Android.mk \ + jni/player.c \ + res/layout/main.xml \ + res/values/strings.xml \ + src/org/freedesktop/gstreamer/Player.java \ + src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ + src/org/freedesktop/gstreamer/player/Play.java + diff --git a/playback/player/gst-play/Makefile b/playback/player/gst-play/Makefile deleted file mode 100644 index ff88941a52..0000000000 --- a/playback/player/gst-play/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -CFLAGS ?= -Wall -O2 -g -PKGCONFIG ?= pkg-config -LIBS := $(shell $(PKGCONFIG) --libs --cflags gstreamer-1.0 gstreamer-video-1.0) -lm - -all: gst-play - -clean: - rm -f gst-play - -SOURCES = \ - gst-play.c \ - gst-play-kb.c \ - ../lib/gst/player/gstplayer.c - -HEADERS = \ - gst-play-kb.h \ - ../lib/gst/player/gstplayer.h - -gst-play: $(SOURCES) $(HEADERS) - $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib - diff --git a/playback/player/gst-play/Makefile.am b/playback/player/gst-play/Makefile.am new file mode 100644 index 0000000000..534e0df83f --- /dev/null +++ b/playback/player/gst-play/Makefile.am @@ -0,0 +1,10 @@ +bin_PROGRAMS = gst-play + +gst_play_SOURCES = gst-play.c gst-play-kb.c gst-play-kb.h + +LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ + $(GSTREAMER_LIBS) $(GLIB_LIBS) $(LIBM) + +AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GLIB_CFLAGS) $(WARNING_CFLAGS) + +noinst_HEADERS = gst-play-kb.h diff --git a/playback/player/gtk/Makefile b/playback/player/gtk/Makefile deleted file mode 100644 index a17c45ed30..0000000000 --- a/playback/player/gtk/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -CFLAGS ?= -Wall -O2 -g -PKGCONFIG ?= pkg-config -LIBS := $(shell $(PKGCONFIG) --libs --cflags gstreamer-1.0 gstreamer-video-1.0 gtk+-3.0) -lm - -all: gtk-play - -clean: - rm -f gtk-play - -SOURCES = \ - gtk-play.c \ - ../lib/gst/player/gstplayer.c - -HEADERS = \ - ../lib/gst/player/gstplayer.h - -gtk-play: $(SOURCES) $(HEADERS) - $(CC) -o $@ $(SOURCES) $(CFLAGS) $(LIBS) -I../lib - diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am new file mode 100644 index 0000000000..03068e6227 --- /dev/null +++ b/playback/player/gtk/Makefile.am @@ -0,0 +1,10 @@ +bin_PROGRAMS = gtk-play + +gtk_play_SOURCES = gtk-play.c + +LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ + $(GSTREAMER_LIBS) $(GTK_LIBS) $(GLIB_LIBS) $(LIBM) + +AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GLIB_CFLAGS) + +noinst_HEADERS = diff --git a/playback/player/ios/Makefile.am b/playback/player/ios/Makefile.am new file mode 100644 index 0000000000..c6da505121 --- /dev/null +++ b/playback/player/ios/Makefile.am @@ -0,0 +1,22 @@ +EXTRA_DIST = \ + GstPlay/AppDelegate.h \ + GstPlay/AppDelegate.m \ + GstPlay/EaglUIVIew.h \ + GstPlay/EaglUIVIew.m \ + GstPlay/en.lproj/InfoPlist.strings \ + GstPlay/fonts.conf \ + GstPlay/gst_ios_init.h \ + GstPlay/gst_ios_init.m \ + GstPlay/GstPlay-Info.plist \ + GstPlay/GstPlay-Prefix.pch \ + GstPlay/LibraryViewController.h \ + GstPlay/LibraryViewController.m \ + GstPlay/main.m \ + GstPlay/MainStoryboard_iPad.storyboard \ + GstPlay/MainStoryboard_iPhone.storyboard \ + GstPlay/Ubuntu-R.ttf \ + GstPlay/VideoViewController.h \ + GstPlay/VideoViewController.m \ + GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata \ + GstPlay.xcodeproj/project.pbxproj + From 4b9da857c08f97095e037319b9e42261a57ca794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 3 Aug 2014 12:47:27 +0200 Subject: [PATCH 021/412] playback/player: Add a single-include header --- playback/player/gst-play/gst-play.c | 2 +- playback/player/gtk/gtk-play.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 5154d7ac37..0e3dda14a2 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -29,7 +29,7 @@ #include #include "gst-play-kb.h" -#include +#include #define VOLUME_STEPS 20 diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 7bbc7a809f..5076ff6c47 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -14,7 +14,7 @@ #include -#include +#include typedef struct { From e644c61286078ecde967e631c76aefaebbe3c2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 3 Aug 2014 15:10:13 +0200 Subject: [PATCH 022/412] playback/player: Always notify about video dimension changes Without video we will notify width=height=0 --- playback/player/gtk/gtk-play.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 5076ff6c47..61af9baef3 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -154,7 +154,10 @@ static void video_dimensions_changed_cb (GstPlayer * unused, gint width, gint height, GtkPlay * play) { - gtk_widget_show (play->video_area); + if (width > 0 && height > 0) + gtk_widget_show (play->video_area); + else + gtk_widget_hide (play->video_area); } int From e85d26115a0a35e0d843b504964f93eb200a5b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 3 Aug 2014 16:08:14 +0200 Subject: [PATCH 023/412] playback/player: Move dispatch-to-main-context out of the constructor and make it a normal property --- playback/player/android/jni/player.c | 2 +- playback/player/gst-play/gst-play.c | 3 ++- playback/player/gtk/gtk-play.c | 4 +++- playback/player/ios/GstPlay/VideoViewController.m | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index e0e2d22913..bdf8e312e0 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -193,7 +193,7 @@ native_new (JNIEnv * env, jobject thiz) { Player *player = g_slice_new0 (Player); - player->player = gst_player_new (FALSE); + player->player = gst_player_new (); SET_CUSTOM_DATA (env, thiz, native_player_field_id, player); player->java_player = (*env)->NewGlobalRef (env, thiz); diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 0e3dda14a2..b9acb65fb6 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -114,8 +114,9 @@ play_new (gchar ** uris, gdouble initial_volume) play->num_uris = g_strv_length (uris); play->cur_idx = -1; - play->player = gst_player_new (TRUE); + play->player = gst_player_new (); + g_object_set (play->player, "dispatch-to-main-context", TRUE, NULL); g_signal_connect (play->player, "position-updated", G_CALLBACK (position_updated_cb), play); g_signal_connect (play->player, "end-of-stream", diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 61af9baef3..d4f8e1ad7d 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -191,7 +191,9 @@ main (gint argc, gchar ** argv) return 1; } - play.player = gst_player_new (TRUE); + play.player = gst_player_new (); + + g_object_set (play.player, "dispatch-to-main-context", TRUE, NULL); if (!gst_uri_is_valid (play.uri)) { gchar *uri = gst_filename_to_uri (play.uri, NULL); diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index 7863f90db1..150c72aae0 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -63,7 +63,7 @@ media_width = 320; media_height = 240; - player = gst_player_new(FALSE); + player = gst_player_new(); g_object_set (player, "window-handle", video_view, NULL); g_object_set (player, "uri", [uri UTF8String], NULL); From 038cd47bfb94987432760c09d074ba0fed8acdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 3 Aug 2014 16:54:27 +0200 Subject: [PATCH 024/412] playback/player: Add our own error domain and code --- playback/player/android/build.xml | 92 +++++++++++++++++++ playback/player/android/jni/player.c | 7 +- playback/player/android/res/layout/main.xml | 62 +++++++++++++ .../player/android/res/values/strings.xml | 6 ++ .../src/org/freedesktop/gstreamer/Player.java | 14 ++- 5 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 playback/player/android/build.xml create mode 100644 playback/player/android/res/layout/main.xml create mode 100644 playback/player/android/res/values/strings.xml diff --git a/playback/player/android/build.xml b/playback/player/android/build.xml new file mode 100644 index 0000000000..4ef18c8f98 --- /dev/null +++ b/playback/player/android/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index bdf8e312e0..fe7f29d9b4 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -161,11 +161,10 @@ on_error (GstPlayer * unused, GError * err, Player * player) JNIEnv *env = get_jni_env (); jstring error_msg; - // FIXME - error_msg = err ? (*env)->NewStringUTF (env, err->message) : NULL; + error_msg =(*env)->NewStringUTF (env, err->message); (*env)->CallVoidMethod (env, player->java_player, on_error_method_id, - error_msg); + err->code, error_msg); if ((*env)->ExceptionCheck (env)) { (*env)->ExceptionDescribe (env); (*env)->ExceptionClear (env); @@ -427,7 +426,7 @@ native_class_init (JNIEnv * env, jclass klass) on_seek_finished_method_id = (*env)->GetMethodID (env, klass, "onSeekFinished", "()V"); on_error_method_id = - (*env)->GetMethodID (env, klass, "onError", "(Ljava/lang/String;)V"); + (*env)->GetMethodID (env, klass, "onError", "(ILjava/lang/String;)V"); on_video_dimensions_changed_method_id = (*env)->GetMethodID (env, klass, "onVideoDimensionsChanged", "(II)V"); diff --git a/playback/player/android/res/layout/main.xml b/playback/player/android/res/layout/main.xml new file mode 100644 index 0000000000..b745d8069e --- /dev/null +++ b/playback/player/android/res/layout/main.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/res/values/strings.xml b/playback/player/android/res/values/strings.xml new file mode 100644 index 0000000000..9587e3c40c --- /dev/null +++ b/playback/player/android/res/values/strings.xml @@ -0,0 +1,6 @@ + + + GStreamer Play + Play + Pause + diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java index 862444ba03..993001a759 100644 --- a/playback/player/android/src/org/freedesktop/gstreamer/Player.java +++ b/playback/player/android/src/org/freedesktop/gstreamer/Player.java @@ -183,9 +183,14 @@ public class Player implements Closeable { } } + // Keep these in sync with gstplayer.h + private static final Error[] errorMap = {Error.FAILED}; + public enum Error { + FAILED + } + public static interface ErrorListener { - // FIXME: enums - abstract void error(Player player, String errorMessage); + abstract void error(Player player, Error error, String errorMessage); } private ErrorListener errorListener; @@ -193,9 +198,10 @@ public class Player implements Closeable { errorListener = listener; } - private void onError(String errorMessage) { + private void onError(int errorCode, String errorMessage) { if (errorListener != null) { - errorListener.error(this, errorMessage); + Error error = errorMap[errorCode]; + errorListener.error(this, error, errorMessage); } } From 7cb352aaa6868bc9ce1bd09f58f9403f592d3b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 5 Aug 2014 19:12:02 +0200 Subject: [PATCH 025/412] playback/player: Implement seek throttling --- playback/player/android/jni/player.c | 19 +------------------ .../src/org/freedesktop/gstreamer/Player.java | 15 --------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index fe7f29d9b4..d9f520c38c 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -56,7 +56,6 @@ static jfieldID native_player_field_id; static jmethodID on_position_updated_method_id; static jmethodID on_duration_changed_method_id; static jmethodID on_end_of_stream_method_id; -static jmethodID on_seek_finished_method_id; static jmethodID on_error_method_id; static jmethodID on_video_dimensions_changed_method_id; @@ -143,18 +142,6 @@ on_end_of_stream (GstPlayer * unused, Player * player) } } -static void -on_seek_finished (GstPlayer * unused, Player * player) -{ - JNIEnv *env = get_jni_env (); - - (*env)->CallVoidMethod (env, player->java_player, on_seek_finished_method_id); - if ((*env)->ExceptionCheck (env)) { - (*env)->ExceptionDescribe (env); - (*env)->ExceptionClear (env); - } -} - static void on_error (GstPlayer * unused, GError * err, Player * player) { @@ -202,8 +189,6 @@ native_new (JNIEnv * env, jobject thiz) G_CALLBACK (on_duration_changed), player); g_signal_connect (player->player, "end-of-stream", G_CALLBACK (on_end_of_stream), player); - g_signal_connect (player->player, "seek-finished", - G_CALLBACK (on_seek_finished), player); g_signal_connect (player->player, "error", G_CALLBACK (on_error), player); g_signal_connect (player->player, "video-dimensions-changed", G_CALLBACK (on_video_dimensions_changed), player); @@ -423,8 +408,6 @@ native_class_init (JNIEnv * env, jclass klass) (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V"); on_end_of_stream_method_id = (*env)->GetMethodID (env, klass, "onEndOfStream", "()V"); - on_seek_finished_method_id = - (*env)->GetMethodID (env, klass, "onSeekFinished", "()V"); on_error_method_id = (*env)->GetMethodID (env, klass, "onError", "(ILjava/lang/String;)V"); on_video_dimensions_changed_method_id = @@ -432,7 +415,7 @@ native_class_init (JNIEnv * env, jclass klass) if (!native_player_field_id || !on_position_updated_method_id || !on_duration_changed_method_id || - !on_end_of_stream_method_id || !on_seek_finished_method_id || + !on_end_of_stream_method_id || !on_error_method_id || !on_video_dimensions_changed_method_id) { static const gchar *message = "The calling class does not implement all necessary interface methods"; diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java index 993001a759..d93304d92d 100644 --- a/playback/player/android/src/org/freedesktop/gstreamer/Player.java +++ b/playback/player/android/src/org/freedesktop/gstreamer/Player.java @@ -168,21 +168,6 @@ public class Player implements Closeable { } } - public static interface SeekFinishedListener { - abstract void seekFinished(Player player); - } - - private SeekFinishedListener seekFinishedListener; - public void setSeekFinishedListener(SeekFinishedListener listener) { - seekFinishedListener = listener; - } - - private void onSeekFinished() { - if (seekFinishedListener != null) { - seekFinishedListener.seekFinished(this); - } - } - // Keep these in sync with gstplayer.h private static final Error[] errorMap = {Error.FAILED}; public enum Error { From a622cb0d2cacdca40cedb2232b555097546c9cf8 Mon Sep 17 00:00:00 2001 From: Parthasarathi Susarla Date: Wed, 6 Aug 2014 17:37:33 +1000 Subject: [PATCH 026/412] playback/player: GTK: Improvements to the GTK+ player This patch contains a few improvements to the gtk-player: * Uses unified play/pause button. * Adds a Skip Previous/Next button. * Volume control * Takes multiple files/uri's as arguments. * Switches to the next track (if any) when the current track reaches and end. https://github.com/sdroege/gst-player/pull/1 --- playback/player/gtk/gtk-play.c | 202 ++++++++++++++++++++++++++++----- 1 file changed, 175 insertions(+), 27 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index d4f8e1ad7d..502e85402b 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -16,18 +16,35 @@ #include +#define APP_NAME "gtk-play" + typedef struct { GstPlayer *player; gchar *uri; + GList *uris; + GtkWidget *window; - GtkWidget *play_button, *pause_button; + GtkWidget *play_pause_button; + GtkWidget *prev_button, *next_button; GtkWidget *seekbar; GtkWidget *video_area; + GtkWidget *volume_button; gulong seekbar_value_changed_signal_id; } GtkPlay; + +static void +set_title (GtkPlay *play, const gchar *title) +{ + if (title == NULL) { + gtk_window_set_title (GTK_WINDOW (play->window), APP_NAME); + } else { + gtk_window_set_title (GTK_WINDOW (play->window), title); + } +} + static void delete_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { @@ -55,15 +72,70 @@ video_area_realize_cb (GtkWidget * widget, GtkPlay * play) } static void -play_clicked_cb (GtkButton * button, GtkPlay * play) +play_pause_clicked_cb (GtkButton *button, GtkPlay *play) { - gst_player_play (play->player); + GtkWidget *image; + + if (gst_player_is_playing (play->player)) { + gst_player_pause (play->player); + image = gtk_image_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON(play->play_pause_button), image); + } else { + gchar *title; + + gst_player_play (play->player); + image = gtk_image_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON(play->play_pause_button), image); + + title = gst_player_get_uri (play->player); + set_title (play, title); + g_free (title); + } } static void -pause_clicked_cb (GtkButton * button, GtkPlay * play) +skip_prev_clicked_cb (GtkButton *button, GtkPlay *play) { - gst_player_pause (play->player); + GList *prev; + gchar *cur_uri; + + prev = g_list_find_custom (play->uris, + gst_player_get_uri (play->player), + (GCompareFunc)strcmp); + if (prev != NULL) { + prev = g_list_previous (prev); + + if (prev != NULL) { + gtk_widget_set_sensitive (play->next_button, TRUE); + gst_player_set_uri (play->player, prev->data); + gst_player_play (play->player); + set_title (play, prev->data); + } else { + gtk_widget_set_sensitive (play->prev_button, FALSE); + } + } +} + +static void +skip_next_clicked_cb (GtkButton *button, GtkPlay *play) +{ + GList *next, *l; + gchar *cur_uri; + + next = g_list_find_custom (play->uris, + gst_player_get_uri (play->player), + (GCompareFunc)strcmp); + if (next != NULL) { + next = g_list_next (next); + if (next != NULL) { + gtk_widget_set_sensitive (play->prev_button, TRUE); + gst_player_set_uri (play->player, next->data); + gst_player_play (play->player); + set_title (play, next->data); + } else { + gtk_widget_set_sensitive (play->next_button, FALSE); + } + } } static void @@ -73,6 +145,14 @@ seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) gst_player_seek (play->player, gst_util_uint64_scale (value, GST_SECOND, 1)); } +void +volume_changed_cb (GtkScaleButton *button, + gdouble value, + GtkPlay *play) +{ + gst_player_set_volume (play->player, value); +} + static void create_ui (GtkPlay * play) { @@ -81,23 +161,19 @@ create_ui (GtkPlay * play) play->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (play->window), "delete-event", G_CALLBACK (delete_event_cb), play); + set_title (play, APP_NAME); play->video_area = gtk_drawing_area_new (); gtk_widget_set_double_buffered (play->video_area, FALSE); g_signal_connect (play->video_area, "realize", G_CALLBACK (video_area_realize_cb), play); - play->play_button = - gtk_button_new_from_icon_name ("media-playback-start", - GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->play_button), "clicked", - G_CALLBACK (play_clicked_cb), play); - - play->pause_button = + /* Unified play/pause button */ + play->play_pause_button = gtk_button_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->pause_button), "clicked", - G_CALLBACK (pause_clicked_cb), play); + g_signal_connect (G_OBJECT (play->play_pause_button), "clicked", + G_CALLBACK (play_pause_clicked_cb), play); play->seekbar = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); @@ -106,10 +182,35 @@ create_ui (GtkPlay * play) g_signal_connect (G_OBJECT (play->seekbar), "value-changed", G_CALLBACK (seekbar_value_changed_cb), play); + /* Skip backward button */ + play->prev_button = + gtk_button_new_from_icon_name ("media-skip-backward", + GTK_ICON_SIZE_BUTTON); + g_signal_connect (G_OBJECT (play->prev_button), "clicked", + G_CALLBACK (skip_prev_clicked_cb), play); + gtk_widget_set_sensitive (play->prev_button, FALSE); + + /* Skip forward button */ + play->next_button = + gtk_button_new_from_icon_name ("media-skip-forward", + GTK_ICON_SIZE_BUTTON); + g_signal_connect (G_OBJECT (play->next_button), "clicked", + G_CALLBACK (skip_next_clicked_cb), play); + gtk_widget_set_sensitive (play->next_button, FALSE); + + /* Volume control button */ + play->volume_button = gtk_volume_button_new (); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), + gst_player_get_volume (play->player)); + g_signal_connect (G_OBJECT (play->volume_button), "value-changed", + G_CALLBACK (volume_changed_cb), play); + controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start (GTK_BOX (controls), play->play_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->pause_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); @@ -130,6 +231,7 @@ static void play_clear (GtkPlay * play) { g_free (play->uri); + g_list_free_full (play->uris, g_free); g_object_unref (play->player); } @@ -160,19 +262,49 @@ video_dimensions_changed_cb (GstPlayer * unused, gint width, gint height, gtk_widget_hide (play->video_area); } +static void +eos_cb (GstPlayer * unused, GtkPlay * play) +{ + if (gst_player_is_playing (play->player)) { + GList *next = NULL; + gchar *uri; + + next = g_list_find_custom (play->uris, + gst_player_get_uri (play->player), + (GCompareFunc) strcmp); + if (next) { + next = g_list_next (next); + if (next) { + if (!gtk_widget_is_sensitive(play->prev_button)) + gtk_widget_set_sensitive (play->prev_button, TRUE); + gst_player_set_uri (play->player, next->data); + gst_player_play (play->player); + set_title (play, next->data); + } + else + gst_player_stop (play->player); + } + } +} + int main (gint argc, gchar ** argv) { GtkPlay play; + gchar **file_names = NULL; GOptionContext *ctx; GOptionEntry options[] = { + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, + "Files to play" }, {NULL} }; + guint list_length = 0; GError *err = NULL; + GList *l; memset (&play, 0, sizeof (play)); - g_set_prgname ("gtk-play"); + g_set_prgname (APP_NAME); ctx = g_option_context_new ("FILE|URI"); g_option_context_add_main_entries (ctx, options, NULL); @@ -185,33 +317,49 @@ main (gint argc, gchar ** argv) g_option_context_free (ctx); // FIXME: Add support for playlists and stuff - play.uri = g_strdup (argv[1]); - if (!play.uri) { - g_print ("Usage: %s FILE|URI\n", argv[0]); + /* Parse the list of the file names we have to play. */ + if (!file_names) { + g_print ("Usage: %s FILE(s)|URI(s)\n", APP_NAME); return 1; + } else { + guint i; + + list_length = g_strv_length (file_names); + for (i = 0; i < list_length; i++) { + play.uris = + g_list_append (play.uris, + gst_uri_is_valid (file_names[i]) ? + g_strdup (file_names[i]) : + gst_filename_to_uri (file_names[i], NULL)); + } + + g_strfreev (file_names); + file_names = NULL; } play.player = gst_player_new (); g_object_set (play.player, "dispatch-to-main-context", TRUE, NULL); - if (!gst_uri_is_valid (play.uri)) { - gchar *uri = gst_filename_to_uri (play.uri, NULL); - g_free (play.uri); - play.uri = uri; - } - g_object_set (play.player, "uri", play.uri, NULL); + gst_player_set_uri (play.player, g_list_first (play.uris)->data); create_ui (&play); + if (list_length > 1) + gtk_widget_set_sensitive (play.next_button, TRUE); + g_signal_connect (play.player, "position-updated", G_CALLBACK (position_updated_cb), &play); g_signal_connect (play.player, "duration-changed", G_CALLBACK (duration_changed_cb), &play); g_signal_connect (play.player, "video-dimensions-changed", G_CALLBACK (video_dimensions_changed_cb), &play); + g_signal_connect (play.player, "end-of-stream", + G_CALLBACK (eos_cb), &play); - gst_player_pause (play.player); + /* We have file(s) that need playing. */ + set_title (&play, g_list_first (play.uris)->data); + gst_player_play (play.player); gtk_main (); From ead64d43fa5c5cb3f31ec9dae2999967ad29f17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 6 Aug 2014 16:41:58 +0200 Subject: [PATCH 027/412] playback/player: GTK: Fix logic for setting the prev/next button insensitive at the end/beginning of the playlist --- playback/player/gtk/gtk-play.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 502e85402b..03955040c3 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -110,8 +110,7 @@ skip_prev_clicked_cb (GtkButton *button, GtkPlay *play) gst_player_set_uri (play->player, prev->data); gst_player_play (play->player); set_title (play, prev->data); - } else { - gtk_widget_set_sensitive (play->prev_button, FALSE); + gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); } } } @@ -132,8 +131,7 @@ skip_next_clicked_cb (GtkButton *button, GtkPlay *play) gst_player_set_uri (play->player, next->data); gst_player_play (play->player); set_title (play, next->data); - } else { - gtk_widget_set_sensitive (play->next_button, FALSE); + gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); } } } From ad694a24eaf934f03c7d1a708f9c1f042003d377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 6 Aug 2014 16:44:24 +0200 Subject: [PATCH 028/412] playback/player: GTK: Turn checks that must not fail into assertions in prev/next logic --- playback/player/gtk/gtk-play.c | 39 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 03955040c3..5f31f104f4 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -102,17 +102,16 @@ skip_prev_clicked_cb (GtkButton *button, GtkPlay *play) prev = g_list_find_custom (play->uris, gst_player_get_uri (play->player), (GCompareFunc)strcmp); - if (prev != NULL) { - prev = g_list_previous (prev); - if (prev != NULL) { - gtk_widget_set_sensitive (play->next_button, TRUE); - gst_player_set_uri (play->player, prev->data); - gst_player_play (play->player); - set_title (play, prev->data); - gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); - } - } + g_return_if_fail (prev != NULL); + prev = g_list_previous (prev); + g_return_if_fail (prev != NULL); + + gtk_widget_set_sensitive (play->next_button, TRUE); + gst_player_set_uri (play->player, prev->data); + gst_player_play (play->player); + set_title (play, prev->data); + gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); } static void @@ -124,16 +123,16 @@ skip_next_clicked_cb (GtkButton *button, GtkPlay *play) next = g_list_find_custom (play->uris, gst_player_get_uri (play->player), (GCompareFunc)strcmp); - if (next != NULL) { - next = g_list_next (next); - if (next != NULL) { - gtk_widget_set_sensitive (play->prev_button, TRUE); - gst_player_set_uri (play->player, next->data); - gst_player_play (play->player); - set_title (play, next->data); - gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); - } - } + + g_return_if_fail (next != NULL); + next = g_list_next (next); + g_return_if_fail (next != NULL); + + gtk_widget_set_sensitive (play->prev_button, TRUE); + gst_player_set_uri (play->player, next->data); + gst_player_play (play->player); + set_title (play, next->data); + gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); } static void From adf40eeefe6d81913a6939541447cfd5c6141982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 6 Aug 2014 16:52:22 +0200 Subject: [PATCH 029/412] playback/player: GTK: Fix prev/next button insensitivity logic in the EOS handler --- playback/player/gtk/gtk-play.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 5f31f104f4..2df76e1ab9 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -269,16 +269,19 @@ eos_cb (GstPlayer * unused, GtkPlay * play) next = g_list_find_custom (play->uris, gst_player_get_uri (play->player), (GCompareFunc) strcmp); + + g_return_if_fail (next != NULL); + + next = g_list_next (next); if (next) { - next = g_list_next (next); - if (next) { - if (!gtk_widget_is_sensitive(play->prev_button)) - gtk_widget_set_sensitive (play->prev_button, TRUE); - gst_player_set_uri (play->player, next->data); - gst_player_play (play->player); - set_title (play, next->data); - } - else + if (!gtk_widget_is_sensitive(play->prev_button)) + gtk_widget_set_sensitive (play->prev_button, TRUE); + gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); + + gst_player_set_uri (play->player, next->data); + gst_player_play (play->player); + set_title (play, next->data); + } else { gst_player_stop (play->player); } } From d0dc785d7730a51a8de61d30efa5915a8ec0196e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 7 Aug 2014 10:59:37 +0200 Subject: [PATCH 030/412] playback/player: Fix indention with gst-indent --- playback/player/android/jni/player.c | 2 +- playback/player/gtk/gtk-play.c | 74 ++++++++++++++-------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index d9f520c38c..8b3ff24ace 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -148,7 +148,7 @@ on_error (GstPlayer * unused, GError * err, Player * player) JNIEnv *env = get_jni_env (); jstring error_msg; - error_msg =(*env)->NewStringUTF (env, err->message); + error_msg = (*env)->NewStringUTF (env, err->message); (*env)->CallVoidMethod (env, player->java_player, on_error_method_id, err->code, error_msg); diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 2df76e1ab9..f015077ebd 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -36,7 +36,7 @@ typedef struct static void -set_title (GtkPlay *play, const gchar *title) +set_title (GtkPlay * play, const gchar * title) { if (title == NULL) { gtk_window_set_title (GTK_WINDOW (play->window), APP_NAME); @@ -72,20 +72,24 @@ video_area_realize_cb (GtkWidget * widget, GtkPlay * play) } static void -play_pause_clicked_cb (GtkButton *button, GtkPlay *play) +play_pause_clicked_cb (GtkButton * button, GtkPlay * play) { GtkWidget *image; if (gst_player_is_playing (play->player)) { gst_player_pause (play->player); - image = gtk_image_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_BUTTON); - gtk_button_set_image (GTK_BUTTON(play->play_pause_button), image); + image = + gtk_image_new_from_icon_name ("media-playback-start", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); } else { gchar *title; gst_player_play (play->player); - image = gtk_image_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_BUTTON); - gtk_button_set_image (GTK_BUTTON(play->play_pause_button), image); + image = + gtk_image_new_from_icon_name ("media-playback-pause", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); title = gst_player_get_uri (play->player); set_title (play, title); @@ -94,14 +98,13 @@ play_pause_clicked_cb (GtkButton *button, GtkPlay *play) } static void -skip_prev_clicked_cb (GtkButton *button, GtkPlay *play) +skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) { GList *prev; gchar *cur_uri; prev = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), - (GCompareFunc)strcmp); + gst_player_get_uri (play->player), (GCompareFunc) strcmp); g_return_if_fail (prev != NULL); prev = g_list_previous (prev); @@ -115,14 +118,13 @@ skip_prev_clicked_cb (GtkButton *button, GtkPlay *play) } static void -skip_next_clicked_cb (GtkButton *button, GtkPlay *play) +skip_next_clicked_cb (GtkButton * button, GtkPlay * play) { GList *next, *l; gchar *cur_uri; next = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), - (GCompareFunc)strcmp); + gst_player_get_uri (play->player), (GCompareFunc) strcmp); g_return_if_fail (next != NULL); next = g_list_next (next); @@ -143,9 +145,7 @@ seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) } void -volume_changed_cb (GtkScaleButton *button, - gdouble value, - GtkPlay *play) +volume_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) { gst_player_set_volume (play->player, value); } @@ -181,30 +181,31 @@ create_ui (GtkPlay * play) /* Skip backward button */ play->prev_button = - gtk_button_new_from_icon_name ("media-skip-backward", - GTK_ICON_SIZE_BUTTON); + gtk_button_new_from_icon_name ("media-skip-backward", + GTK_ICON_SIZE_BUTTON); g_signal_connect (G_OBJECT (play->prev_button), "clicked", - G_CALLBACK (skip_prev_clicked_cb), play); + G_CALLBACK (skip_prev_clicked_cb), play); gtk_widget_set_sensitive (play->prev_button, FALSE); /* Skip forward button */ - play->next_button = - gtk_button_new_from_icon_name ("media-skip-forward", - GTK_ICON_SIZE_BUTTON); + play->next_button = + gtk_button_new_from_icon_name ("media-skip-forward", + GTK_ICON_SIZE_BUTTON); g_signal_connect (G_OBJECT (play->next_button), "clicked", - G_CALLBACK (skip_next_clicked_cb), play); + G_CALLBACK (skip_next_clicked_cb), play); gtk_widget_set_sensitive (play->next_button, FALSE); /* Volume control button */ play->volume_button = gtk_volume_button_new (); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), - gst_player_get_volume (play->player)); + gst_player_get_volume (play->player)); g_signal_connect (G_OBJECT (play->volume_button), "value-changed", - G_CALLBACK (volume_changed_cb), play); + G_CALLBACK (volume_changed_cb), play); controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, + 2); gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); @@ -267,14 +268,13 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gchar *uri; next = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), - (GCompareFunc) strcmp); + gst_player_get_uri (play->player), (GCompareFunc) strcmp); g_return_if_fail (next != NULL); next = g_list_next (next); if (next) { - if (!gtk_widget_is_sensitive(play->prev_button)) + if (!gtk_widget_is_sensitive (play->prev_button)) gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); @@ -282,7 +282,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gst_player_play (play->player); set_title (play, next->data); } else { - gst_player_stop (play->player); + gst_player_stop (play->player); } } } @@ -294,8 +294,8 @@ main (gint argc, gchar ** argv) gchar **file_names = NULL; GOptionContext *ctx; GOptionEntry options[] = { - { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, - "Files to play" }, + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, + "Files to play"}, {NULL} }; guint list_length = 0; @@ -327,10 +327,9 @@ main (gint argc, gchar ** argv) list_length = g_strv_length (file_names); for (i = 0; i < list_length; i++) { play.uris = - g_list_append (play.uris, - gst_uri_is_valid (file_names[i]) ? - g_strdup (file_names[i]) : - gst_filename_to_uri (file_names[i], NULL)); + g_list_append (play.uris, + gst_uri_is_valid (file_names[i]) ? + g_strdup (file_names[i]) : gst_filename_to_uri (file_names[i], NULL)); } g_strfreev (file_names); @@ -346,7 +345,7 @@ main (gint argc, gchar ** argv) create_ui (&play); if (list_length > 1) - gtk_widget_set_sensitive (play.next_button, TRUE); + gtk_widget_set_sensitive (play.next_button, TRUE); g_signal_connect (play.player, "position-updated", G_CALLBACK (position_updated_cb), &play); @@ -354,8 +353,7 @@ main (gint argc, gchar ** argv) G_CALLBACK (duration_changed_cb), &play); g_signal_connect (play.player, "video-dimensions-changed", G_CALLBACK (video_dimensions_changed_cb), &play); - g_signal_connect (play.player, "end-of-stream", - G_CALLBACK (eos_cb), &play); + g_signal_connect (play.player, "end-of-stream", G_CALLBACK (eos_cb), &play); /* We have file(s) that need playing. */ set_title (&play, g_list_first (play.uris)->data); From 5a2104e89da99c845f89323e0a078a5a382f3320 Mon Sep 17 00:00:00 2001 From: Guillaume Seguin Date: Fri, 17 Oct 2014 14:39:30 +0200 Subject: [PATCH 031/412] network/http-launch: Add PORT command line argument --- network/http-launch/http-launch.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 4ab7a275b5..63c575a7c2 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -352,14 +352,17 @@ main (gint argc, gchar ** argv) gst_init (&argc, &argv); - if (argc < 2) { - g_print ("usage: %s \n" - "example: %s ( videotestsrc ! theoraenc ! oggmux name=stream )\n", + if (argc < 4) { + g_print ("usage: %s PORT \n" + "example: %s 8080 ( videotestsrc ! theoraenc ! oggmux name=stream )\n", argv[0], argv[0]); return -1; } - bin = gst_parse_launchv ((const gchar **) argv + 1, &err); + const gchar *port_str = argv[1]; + const int port = (int) g_ascii_strtoll(port_str, NULL, 10); + + bin = gst_parse_launchv ((const gchar **) argv + 2, &err); if (!bin) { g_print ("invalid pipeline: %s\n", err->message); g_clear_error (&err); @@ -422,14 +425,14 @@ main (gint argc, gchar ** argv) } service = g_socket_service_new (); - g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), 8080, NULL, + g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), port, NULL, NULL); g_signal_connect (service, "incoming", G_CALLBACK (on_new_connection), NULL); g_socket_service_start (service); - g_print ("Listening on http://127.0.0.1:8080/\n"); + g_print ("Listening on http://127.0.0.1:%d/\n", port); g_main_loop_run (loop); From 28507ed0cfab7f56c5ffc09a8c9d2329da56d278 Mon Sep 17 00:00:00 2001 From: Guillaume Seguin Date: Fri, 17 Oct 2014 14:43:25 +0200 Subject: [PATCH 032/412] network/http-launch: Fix segfault Message len was not updated when iterating in on_read_bytes --- network/http-launch/http-launch.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 63c575a7c2..44871d3927 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -203,10 +203,11 @@ on_read_bytes (GPollableInputStream * stream, Client * client) return FALSE; } else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { guint8 *tmp = client->current_message->data; + guint tmp_len = client->current_message->len; g_clear_error (&err); - while (client->current_message->len > 3) { + while (tmp_len > 3) { if (tmp[0] == 0x0d && tmp[1] == 0x0a && tmp[2] == 0x0d && tmp[3] == 0x0a) { guint len; @@ -215,8 +216,10 @@ on_read_bytes (GPollableInputStream * stream, Client * client) client_message (client, (gchar *) client->current_message->data, len); g_byte_array_remove_range (client->current_message, 0, len); tmp = client->current_message->data; + tmp_len = client->current_message->len; } else { tmp++; + tmp_len--; } } From 97c4cfc6708f5891962718af33e9f5ebd1f7c1ed Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 12 Nov 2014 16:18:28 +0100 Subject: [PATCH 033/412] playback/player: android: Link against GLib 2.0 as it is necessary --- playback/player/android/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 39ad84ee4f..439baa9bcd 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -19,6 +19,6 @@ GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) -GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 glib-2.0 include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk From aaaea24c216f36e7a9d9601c88174dca63ec59f4 Mon Sep 17 00:00:00 2001 From: Victor Sergienko Date: Tue, 27 Jan 2015 16:15:10 +0200 Subject: [PATCH 034/412] network/http-launch: segfault on GStreamer warnings because of wrong parsing function --- network/http-launch/http-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 44871d3927..7071b1e1ed 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -306,7 +306,7 @@ on_message (GstBus * bus, GstMessage * message, gpointer user_data) gchar *debug; GError *err; - gst_message_parse_error (message, &err, &debug); + gst_message_parse_warning (message, &err, &debug); g_print ("Warning %s\n", err->message); g_error_free (err); g_free (debug); From ae46c50138d378bd4dcddaca37e7ee36f51830c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 7 Feb 2015 11:14:42 +0100 Subject: [PATCH 035/412] playback/player: Make state handling more robust and notify the application about our states The application now will get one of 4 states via a signal: STOPPED: After EOS, error and when explicitely stopped BUFFERING: When moving to the lower states, or we get buffering messages, also when seeking. PAUSED and PLAYING: When having reached that state and it's our target Also we now always first go to PAUSED state before we seek, and also before we go to PLAYING. This allows us to deterministically change states and makes everything a bit more robust. As a side-effect, get rid of the is-playing property. Applications can now get this from the corresponding signal if they need to know. Additionally now notify the application about the buffering percentage. Also fix a few bugs related to state handling and buffering. --- playback/player/gst-play/gst-play.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index b9acb65fb6..998a991018 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -76,7 +76,7 @@ error_cb (GstPlayer * player, GError * err, GstPlay * play) } } -static gboolean +static void position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) { GstClockTime dur = -1; @@ -84,10 +84,7 @@ position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) g_object_get (play->player, "duration", &dur, NULL); - if (play->desired_state == GST_STATE_PAUSED) - g_snprintf (status, sizeof (status), "Paused"); - else - memset (status, ' ', sizeof (status) - 1); + memset (status, ' ', sizeof (status) - 1); if (pos >= 0 && dur > 0) { gchar dstr[32], pstr[32]; @@ -99,8 +96,18 @@ position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) dstr[9] = '\0'; g_print ("%s / %s %s\r", pstr, dstr, status); } +} - return TRUE; +static void +state_changed_cb (GstPlayer * player, GstPlayerState state, GstPlay * play) +{ + g_print ("State changed: %s\n", gst_player_state_get_name (state)); +} + +static void +buffering_cb (GstPlayer * player, gint percent, GstPlay * play) +{ + g_print ("Buffering: %d\n", percent); } static GstPlay * @@ -119,6 +126,10 @@ play_new (gchar ** uris, gdouble initial_volume) g_object_set (play->player, "dispatch-to-main-context", TRUE, NULL); g_signal_connect (play->player, "position-updated", G_CALLBACK (position_updated_cb), play); + g_signal_connect (play->player, "state-changed", + G_CALLBACK (state_changed_cb), play); + g_signal_connect (play->player, "buffering", + G_CALLBACK (buffering_cb), play); g_signal_connect (play->player, "end-of-stream", G_CALLBACK (end_of_stream_cb), play); g_signal_connect (play->player, "error", G_CALLBACK (error_cb), play); From d8287305919ef467cf1cdc56ba912167989d9f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 8 Feb 2015 11:38:47 +0100 Subject: [PATCH 036/412] playback/player: Don't skip to next track immediately in gst-play when seeking after the duration Instead wait for end-of-stream for switching. --- playback/player/gst-play/gst-play.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 998a991018..6fc73bbce3 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -329,16 +329,9 @@ relative_seek (GstPlay * play, gdouble percent) goto seek_failed; pos = pos + dur * percent; - if (pos > dur) { - if (!play_next (play)) { - g_print ("\nReached end of play list.\n"); - g_main_loop_quit (play->loop); - } - } else { - if (pos < 0) - pos = 0; - gst_player_seek (play->player, pos); - } + if (pos < 0) + pos = 0; + gst_player_seek (play->player, pos); return; From 9da0c1427371cb2f7498c986577041b07c3f7ed7 Mon Sep 17 00:00:00 2001 From: Luis de Arquer Date: Sat, 21 Feb 2015 12:33:17 +0000 Subject: [PATCH 037/412] network/http-launch: Add support for MJPEG streams using multipartmux --- network/http-launch/http-launch.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 7071b1e1ed..23519f14bf 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -41,6 +41,7 @@ static GList *clients = NULL; static GstElement *pipeline = NULL; static GstElement *multisocketsink = NULL; static gboolean started = FALSE; +static gchar content_type[256]; static void remove_client (Client * client) @@ -107,7 +108,7 @@ client_message (Client * client, const gchar * data, guint len) http_version = "HTTP/1.0"; if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); + response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, content_type); } else { response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); } @@ -126,7 +127,7 @@ client_message (Client * client, const gchar * data, guint len) http_version = "HTTP/1.0"; if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n" "\r\n", http_version); + response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, content_type); ok = TRUE; } else { response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); @@ -378,6 +379,17 @@ main (gint argc, gchar ** argv) gst_object_unref (bin); return -3; } + + /* + * Make the HTTP header 'Content-type' if we are trying to create a + * MJPEG (or any other multipart) stream. + */ + if ( strcmp ("GstMultipartMux", g_type_name(G_OBJECT_TYPE(stream)) ) == 0 ) { + strcpy (content_type, "Content-Type: " + "multipart/x-mixed-replace;boundary=--ThisRandomString\r\n"); + } else { + strcpy (content_type, ""); + } srcpad = gst_element_get_static_pad (stream, "src"); if (!srcpad) { From bb90ddfad112f6b5178afe40befe76c268aed546 Mon Sep 17 00:00:00 2001 From: Luis de Arquer Date: Sat, 21 Feb 2015 13:04:19 +0000 Subject: [PATCH 038/412] network/http-launch: Content-type boundary tag is now taken from the multipartmux "boundary" property --- network/http-launch/http-launch.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 23519f14bf..c2eea6c968 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -41,7 +41,7 @@ static GList *clients = NULL; static GstElement *pipeline = NULL; static GstElement *multisocketsink = NULL; static gboolean started = FALSE; -static gchar content_type[256]; +static gchar *content_type; static void remove_client (Client * client) @@ -384,13 +384,22 @@ main (gint argc, gchar ** argv) * Make the HTTP header 'Content-type' if we are trying to create a * MJPEG (or any other multipart) stream. */ + content_type = g_strdup (""); + if ( strcmp ("GstMultipartMux", g_type_name(G_OBJECT_TYPE(stream)) ) == 0 ) { - strcpy (content_type, "Content-Type: " - "multipart/x-mixed-replace;boundary=--ThisRandomString\r\n"); - } else { - strcpy (content_type, ""); + gchar *boundary = NULL; + g_object_get (stream, "boundary", &boundary, NULL); + if (boundary == NULL) { + g_print ("Warning: \"boundary\" property not found in mutipartmux\n"); + } else { + /* Free default empty string "" created above, and create new string */ + g_free(content_type); + content_type = g_strdup_printf ("Content-Type: " + "multipart/x-mixed-replace;boundary=--%s\r\n", boundary); + } } + srcpad = gst_element_get_static_pad (stream, "src"); if (!srcpad) { g_print ("no \"src\" pad in element \"stream\" found\n"); From d72375c22d070774704adf6856dc942c17493238 Mon Sep 17 00:00:00 2001 From: Luis de Arquer Date: Sat, 21 Feb 2015 13:49:58 +0000 Subject: [PATCH 039/412] network/http-launch: Minor format changes --- network/http-launch/http-launch.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index c2eea6c968..8732326843 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -108,7 +108,8 @@ client_message (Client * client, const gchar * data, guint len) http_version = "HTTP/1.0"; if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, content_type); + response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, + content_type); } else { response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); } @@ -127,7 +128,8 @@ client_message (Client * client, const gchar * data, guint len) http_version = "HTTP/1.0"; if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, content_type); + response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, + content_type); ok = TRUE; } else { response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); @@ -379,9 +381,9 @@ main (gint argc, gchar ** argv) gst_object_unref (bin); return -3; } - - /* - * Make the HTTP header 'Content-type' if we are trying to create a + + /* + * Make the HTTP header 'Content-type' if we are trying to create a * MJPEG (or any other multipart) stream. */ content_type = g_strdup (""); @@ -395,11 +397,10 @@ main (gint argc, gchar ** argv) /* Free default empty string "" created above, and create new string */ g_free(content_type); content_type = g_strdup_printf ("Content-Type: " - "multipart/x-mixed-replace;boundary=--%s\r\n", boundary); + "multipart/x-mixed-replace;boundary=--%s\r\n", boundary); } } - srcpad = gst_element_get_static_pad (stream, "src"); if (!srcpad) { g_print ("no \"src\" pad in element \"stream\" found\n"); From d0ef447da9dba57b1d1e31a1969c606aa1a3458e Mon Sep 17 00:00:00 2001 From: Ross Burton Date: Thu, 26 Feb 2015 17:17:05 +0000 Subject: [PATCH 040/412] playback/player: gtk-play: show a file chooser if no URIs were passed --- playback/player/gtk/gtk-play.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index f015077ebd..9766a72008 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -319,8 +319,32 @@ main (gint argc, gchar ** argv) // FIXME: Add support for playlists and stuff /* Parse the list of the file names we have to play. */ if (!file_names) { - g_print ("Usage: %s FILE(s)|URI(s)\n", APP_NAME); - return 1; + GtkWidget *chooser; + int res; + + chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL); + g_object_set (chooser, + "local-only", FALSE, + "select-multiple", TRUE, + NULL); + + res = gtk_dialog_run (GTK_DIALOG (chooser)); + if (res == GTK_RESPONSE_ACCEPT) { + GSList *l; + + l = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (chooser)); + while (l) { + play.uris = g_list_append (play.uris, l->data); + l = g_slist_delete_link (l, l); + } + } else { + return 0; + } + gtk_widget_destroy (chooser); } else { guint i; From 6f88388819cb74538788d976f4094986925fced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 27 Feb 2015 09:46:02 +0200 Subject: [PATCH 041/412] playback/player: Remove gst_player_is_playing() The current state is now notified via the state-changed signal, and in the GTK UI it was only used to keep track of the desired state. --- playback/player/gtk/gtk-play.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 9766a72008..f405c387e7 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -32,9 +32,9 @@ typedef struct GtkWidget *video_area; GtkWidget *volume_button; gulong seekbar_value_changed_signal_id; + gboolean playing; } GtkPlay; - static void set_title (GtkPlay * play, const gchar * title) { @@ -76,12 +76,13 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) { GtkWidget *image; - if (gst_player_is_playing (play->player)) { + if (play->playing) { gst_player_pause (play->player); image = gtk_image_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); + play->playing = FALSE; } else { gchar *title; @@ -94,6 +95,7 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) title = gst_player_get_uri (play->player); set_title (play, title); g_free (title); + play->playing = TRUE; } } @@ -263,7 +265,7 @@ video_dimensions_changed_cb (GstPlayer * unused, gint width, gint height, static void eos_cb (GstPlayer * unused, GtkPlay * play) { - if (gst_player_is_playing (play->player)) { + if (play->playing) { GList *next = NULL; gchar *uri; @@ -361,6 +363,7 @@ main (gint argc, gchar ** argv) } play.player = gst_player_new (); + play.playing = TRUE; g_object_set (play.player, "dispatch-to-main-context", TRUE, NULL); From 63421594400cba9402fbf243fb85c5222a617e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 1 Mar 2015 13:59:03 +0100 Subject: [PATCH 042/412] playback/player: Fix indention --- playback/player/gtk/gtk-play.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index f405c387e7..dbb4d72239 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -325,14 +325,9 @@ main (gint argc, gchar ** argv) int res; chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - "_Cancel", GTK_RESPONSE_CANCEL, - "_Open", GTK_RESPONSE_ACCEPT, - NULL); - g_object_set (chooser, - "local-only", FALSE, - "select-multiple", TRUE, - NULL); + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); + g_object_set (chooser, "local-only", FALSE, "select-multiple", TRUE, NULL); res = gtk_dialog_run (GTK_DIALOG (chooser)); if (res == GTK_RESPONSE_ACCEPT) { From 0b7383c104b55327127b1a9ba276932732d986db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 1 Mar 2015 13:59:08 +0100 Subject: [PATCH 043/412] playback/player: Make it possible to call play()/pause() after EOS to restart playback from the beginning --- playback/player/gtk/gtk-play.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index dbb4d72239..0a557be123 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -284,7 +284,14 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gst_player_play (play->player); set_title (play, next->data); } else { - gst_player_stop (play->player); + GtkWidget *image; + + gst_player_pause (play->player); + image = + gtk_image_new_from_icon_name ("media-playback-start", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); + play->playing = FALSE; } } } From b7a1198170c13ac4d091feaacb0e8a60e55ac62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 1 Mar 2015 14:04:00 +0100 Subject: [PATCH 044/412] playback/player: Add copyright header to GTK player --- playback/player/gtk/gtk-play.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 0a557be123..e3ebea10f8 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1,3 +1,23 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + #include #include From 503512405055d873d7ef4d4ca80f61345d10757c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 1 Mar 2015 14:20:17 +0100 Subject: [PATCH 045/412] playback/player: Update Android bindings to the latest API --- playback/player/android/jni/player.c | 52 +++++++++++++------ .../src/org/freedesktop/gstreamer/Player.java | 46 +++++++++++++--- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index 8b3ff24ace..50978d90b3 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -55,6 +55,8 @@ static JavaVM *java_vm; static jfieldID native_player_field_id; static jmethodID on_position_updated_method_id; static jmethodID on_duration_changed_method_id; +static jmethodID on_state_changed_method_id; +static jmethodID on_buffering_method_id; static jmethodID on_end_of_stream_method_id; static jmethodID on_error_method_id; static jmethodID on_video_dimensions_changed_method_id; @@ -130,6 +132,32 @@ on_duration_changed (GstPlayer * unused, GstClockTime duration, Player * player) } } +static void +on_state_changed (GstPlayer * unused, GstPlayerState state, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, + on_state_changed_method_id, state); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +static void +on_buffering (GstPlayer * unused, gint percent, Player * player) +{ + JNIEnv *env = get_jni_env (); + + (*env)->CallVoidMethod (env, player->java_player, + on_buffering_method_id, percent); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + static void on_end_of_stream (GstPlayer * unused, Player * player) { @@ -187,6 +215,10 @@ native_new (JNIEnv * env, jobject thiz) G_CALLBACK (on_position_updated), player); g_signal_connect (player->player, "duration-changed", G_CALLBACK (on_duration_changed), player); + g_signal_connect (player->player, "state-changed", + G_CALLBACK (on_state_changed), player); + g_signal_connect (player->player, "buffering", + G_CALLBACK (on_buffering), player); g_signal_connect (player->player, "end-of-stream", G_CALLBACK (on_end_of_stream), player); g_signal_connect (player->player, "error", G_CALLBACK (on_error), player); @@ -283,20 +315,6 @@ native_get_uri (JNIEnv * env, jobject thiz) return uri; } -static jboolean -native_is_playing (JNIEnv * env, jobject thiz) -{ - Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id); - jboolean is_playing; - - if (!player) - return FALSE; - - g_object_get (player->player, "is-playing", &is_playing, NULL); - - return is_playing; -} - static jlong native_get_position (JNIEnv * env, jobject thiz) { @@ -406,6 +424,10 @@ native_class_init (JNIEnv * env, jclass klass) (*env)->GetMethodID (env, klass, "onPositionUpdated", "(J)V"); on_duration_changed_method_id = (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V"); + on_state_changed_method_id = + (*env)->GetMethodID (env, klass, "onStateChanged", "(I)V"); + on_buffering_method_id = + (*env)->GetMethodID (env, klass, "onBuffering", "(I)V"); on_end_of_stream_method_id = (*env)->GetMethodID (env, klass, "onEndOfStream", "()V"); on_error_method_id = @@ -415,6 +437,7 @@ native_class_init (JNIEnv * env, jclass klass) if (!native_player_field_id || !on_position_updated_method_id || !on_duration_changed_method_id || + !on_state_changed_method_id || !on_buffering_method_id || !on_end_of_stream_method_id || !on_error_method_id || !on_video_dimensions_changed_method_id) { static const gchar *message = @@ -437,7 +460,6 @@ static JNINativeMethod native_methods[] = { {"nativeFree", "()V", (void *) native_free}, {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri}, {"nativeSetUri", "(Ljava/lang/String;)V", (void *) native_set_uri}, - {"nativeIsPlaying", "()Z", (void *) native_is_playing}, {"nativeGetPosition", "()J", (void *) native_get_position}, {"nativeGetDuration", "()J", (void *) native_get_duration}, {"nativeGetVolume", "()D", (void *) native_get_volume}, diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java index d93304d92d..e2bef8caff 100644 --- a/playback/player/android/src/org/freedesktop/gstreamer/Player.java +++ b/playback/player/android/src/org/freedesktop/gstreamer/Player.java @@ -1,6 +1,6 @@ /* GStreamer * - * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2014-2015 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -77,11 +77,6 @@ public class Player implements Closeable { nativeSetUri(uri); } - private native boolean nativeIsPlaying(); - public boolean isPlaying() { - return nativeIsPlaying(); - } - private native long nativeGetPosition(); public long getPosition() { return nativeGetPosition(); @@ -153,6 +148,45 @@ public class Player implements Closeable { } } + private static final State[] stateMap = {State.STOPPED, State.BUFFERING, State.PAUSED, State.PLAYING}; + public enum State { + STOPPED, + BUFFERING, + PAUSED, + PLAYING + } + + public static interface StateChangedListener { + abstract void stateChanged(Player player, State state); + } + + private StateChangedListener stateChangedListener; + public void setStateChangedListener(StateChangedListener listener) { + stateChangedListener = listener; + } + + private void onStateChanged(int stateIdx) { + if (stateChangedListener != null) { + State state = stateMap[stateIdx]; + stateChangedListener.stateChanged(this, state); + } + } + + public static interface BufferingListener { + abstract void buffering(Player player, int percent); + } + + private BufferingListener bufferingListener; + public void setBufferingListener(BufferingListener listener) { + bufferingListener = listener; + } + + private void onBuffering(int percent) { + if (bufferingListener != null) { + bufferingListener.buffering(this, percent); + } + } + public static interface EndOfStreamListener { abstract void endOfStream(Player player); } From 8b41422c77ed01b647c9f38926d56c2bd1e90ec6 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 3 Mar 2015 11:56:31 -0500 Subject: [PATCH 046/412] playback/player: android: Cast pointers to gintptr to simplify code --- playback/player/android/jni/player.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index 50978d90b3..2597db447f 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -30,17 +30,8 @@ GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category -/* - * These macros provide a way to store the native pointer to Player, which might be 32 or 64 bits, into - * a jlong, which is always 64 bits, without warnings. - */ -#if GLIB_SIZEOF_VOID_P == 8 -# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(*env)->GetLongField (env, thiz, fieldID) -# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) -#else -# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(jint)(*env)->GetLongField (env, thiz, fieldID) -# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) -#endif +#define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(gintptr)(*env)->GetLongField (env, thiz, fieldID) +#define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(gintptr)data) typedef struct _Player { From 8ba8714b20a5f53af34417d2990db7ce8505ed0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 6 Mar 2015 09:38:16 +0100 Subject: [PATCH 047/412] playback/player: Stop using GSlice for allocations, it's deprecated soon and slower than malloc in most places --- playback/player/android/jni/player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index 2597db447f..577fab9d3b 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -196,7 +196,7 @@ on_video_dimensions_changed (GstPlayer * unused, gint width, gint height, static void native_new (JNIEnv * env, jobject thiz) { - Player *player = g_slice_new0 (Player); + Player *player = g_new0 (Player, 1); player->player = gst_player_new (); SET_CUSTOM_DATA (env, thiz, native_player_field_id, player); @@ -226,7 +226,7 @@ native_free (JNIEnv * env, jobject thiz) return; (*env)->DeleteGlobalRef (env, player->java_player); - g_slice_free (Player, player); + g_free (player); SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL); } From ebe2883488b39f7311fdb65875f844e752239a7b Mon Sep 17 00:00:00 2001 From: Luis de Arquer Date: Wed, 18 Mar 2015 21:39:26 +0000 Subject: [PATCH 048/412] network/http-launch: Content-type header creation now happens into notify::caps signal handler --- network/http-launch/http-launch.c | 75 +++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 8732326843..38b4400627 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -35,6 +35,12 @@ typedef struct GByteArray *current_message; } Client; +static const char *known_mimetypes[] = { + "video/webm", + "multipart/x-mixed-replace", + NULL +}; + static GMainLoop *loop = NULL; G_LOCK_DEFINE_STATIC (clients); static GList *clients = NULL; @@ -347,6 +353,51 @@ on_client_socket_removed (GstElement * element, GSocket * socket, remove_client (client); } +static void on_stream_caps_changed (GObject *obj, GParamSpec *pspec, + gpointer user_data) +{ + GstPad *src_pad; + GstCaps *src_caps; + GstStructure *gstrc; + + src_pad = (GstPad *) obj; + src_caps = gst_pad_get_current_caps (src_pad); + gstrc = gst_caps_get_structure (src_caps, 0); + + /* + * Include a Content-type header in the case we know the mime + * type is OK in HTTP. Required for MJPEG streams. + */ + int i = 0; + const gchar *mimetype = gst_structure_get_name(gstrc); + while (known_mimetypes[i] != NULL) + { + if (strcmp(mimetype, known_mimetypes[i]) == 0) + { + if (content_type) + g_free(content_type); + + /* Handle the (maybe not so) especial case of multipart to add boundary */ + if (strcmp(mimetype, "multipart/x-mixed-replace") == 0 && + gst_structure_has_field_typed(gstrc, "boundary", G_TYPE_STRING)) + { + const gchar *boundary = gst_structure_get_string(gstrc, "boundary"); + content_type = g_strdup_printf ("Content-Type: " + "multipart/x-mixed-replace;boundary=--%s\r\n", boundary); + } + else + { + content_type = g_strdup_printf ("Content-Type: %s\r\n", mimetype); + } + g_print("%s", content_type); + break; + } + i++; + } + + gst_caps_unref (src_caps); +} + int main (gint argc, gchar ** argv) { @@ -382,25 +433,6 @@ main (gint argc, gchar ** argv) return -3; } - /* - * Make the HTTP header 'Content-type' if we are trying to create a - * MJPEG (or any other multipart) stream. - */ - content_type = g_strdup (""); - - if ( strcmp ("GstMultipartMux", g_type_name(G_OBJECT_TYPE(stream)) ) == 0 ) { - gchar *boundary = NULL; - g_object_get (stream, "boundary", &boundary, NULL); - if (boundary == NULL) { - g_print ("Warning: \"boundary\" property not found in mutipartmux\n"); - } else { - /* Free default empty string "" created above, and create new string */ - g_free(content_type); - content_type = g_strdup_printf ("Content-Type: " - "multipart/x-mixed-replace;boundary=--%s\r\n", boundary); - } - } - srcpad = gst_element_get_static_pad (stream, "src"); if (!srcpad) { g_print ("no \"src\" pad in element \"stream\" found\n"); @@ -409,6 +441,11 @@ main (gint argc, gchar ** argv) return -4; } + content_type = g_strdup (""); + g_signal_connect (srcpad, "notify::caps", + G_CALLBACK (on_stream_caps_changed), + NULL); + ghostpad = gst_ghost_pad_new ("src", srcpad); gst_element_add_pad (GST_ELEMENT (bin), ghostpad); gst_object_unref (srcpad); From 7638d52c47827a78e2e165c108f3baf2e6ed84c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 22 Apr 2015 09:35:01 +0200 Subject: [PATCH 049/412] playback/player: Add VideoToolbox to the iOS project linker flags --- playback/player/ios/GstPlay.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj index 14facf3dd9..6b1d3fafdc 100644 --- a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -347,6 +347,8 @@ CoreAudio, "-framework", AudioToolbox, + "-weak_framework", + VideoToolbox, "-framework", OpenGLES, "-framework", @@ -396,6 +398,8 @@ CoreAudio, "-framework", AudioToolbox, + "-weak_framework", + VideoToolbox, "-framework", OpenGLES, "-framework", From 109e63d7d77d5b60e63368e5ce6e9f70b463cfc5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 22 Apr 2015 14:43:07 +0100 Subject: [PATCH 050/412] playback/player: ios: Extract online media into plist https://github.com/sdroege/gst-player/pull/20 --- .../ios/GstPlay.xcodeproj/project.pbxproj | 4 +++ .../ios/GstPlay/LibraryViewController.m | 31 +------------------ playback/player/ios/GstPlay/OnlineMedia.plist | 30 ++++++++++++++++++ 3 files changed, 35 insertions(+), 30 deletions(-) create mode 100644 playback/player/ios/GstPlay/OnlineMedia.plist diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj index 6b1d3fafdc..ee94d2d707 100644 --- a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 19A17EFC8D41A75E8D3DB72E /* OnlineMedia.plist in Resources */ = {isa = PBXBuildFile; fileRef = 19A176455D27298FE4041DC3 /* OnlineMedia.plist */; }; AD2B881F198D631B0070367B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B881E198D631B0070367B /* Foundation.framework */; }; AD2B8821198D631B0070367B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B8820198D631B0070367B /* CoreGraphics.framework */; }; AD2B8823198D631B0070367B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2B8822198D631B0070367B /* UIKit.framework */; }; @@ -26,6 +27,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 19A176455D27298FE4041DC3 /* OnlineMedia.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = OnlineMedia.plist; sourceTree = ""; }; AD2B881B198D631B0070367B /* GstPlay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GstPlay.app; sourceTree = BUILT_PRODUCTS_DIR; }; AD2B881E198D631B0070367B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; AD2B8820198D631B0070367B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -113,6 +115,7 @@ AD2B885F198D65780070367B /* VideoViewController.h */, AD2B8860198D65780070367B /* VideoViewController.m */, AD2B8827198D631B0070367B /* Supporting Files */, + 19A176455D27298FE4041DC3 /* OnlineMedia.plist */, ); path = GstPlay; sourceTree = ""; @@ -206,6 +209,7 @@ AD2B8835198D631B0070367B /* Ubuntu-R.ttf in Resources */, AD2B8833198D631B0070367B /* fonts.conf in Resources */, AD2B882B198D631B0070367B /* InfoPlist.strings in Resources */, + 19A17EFC8D41A75E8D3DB72E /* OnlineMedia.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/playback/player/ios/GstPlay/LibraryViewController.m b/playback/player/ios/GstPlay/LibraryViewController.m index f1aead5648..330328f22f 100644 --- a/playback/player/ios/GstPlay/LibraryViewController.m +++ b/playback/player/ios/GstPlay/LibraryViewController.m @@ -130,36 +130,7 @@ static NSString *CellIdentifier = @"CellIdentifier"; [entries addObject:[NSString stringWithFormat:@"file://%@/%@", docsPath, e]]; } self->mediaEntries = entries; - - /* Hardcoded list of Online media files */ - entries = [[NSMutableArray alloc] init]; - - // Big Buck Bunny - [entries addObject:@"http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi"]; - [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov"]; - [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg"]; - [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi"]; - - // Sintel - [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv"]; - [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.mp4"]; - [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.ogv"]; - [entries addObject:@"http://mirrorblender.top-ix.org/movies/sintel-1024-surround.mp4"]; - - // Tears of Steel - [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mkv"]; - [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mov"]; - [entries addObject:@"http://media.xiph.org/mango/tears_of_steel_1080p.webm"]; - - // Radio stations - [entries addObject:@"http://radio.hbr1.com:19800/trance.ogg"]; - [entries addObject:@"http://radio.hbr1.com:19800/tronic.aac"]; - - // Non-existing entries (to debug error reporting facilities) - [entries addObject:@"http://non-existing.org/Non_Existing_Server"]; - [entries addObject:@"http://docs.gstreamer.com/Non_Existing_File"]; - - self->onlineEntries = entries; + self->onlineEntries = [NSArray arrayWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"OnlineMedia" withExtension:@"plist"]]; } @end diff --git a/playback/player/ios/GstPlay/OnlineMedia.plist b/playback/player/ios/GstPlay/OnlineMedia.plist new file mode 100644 index 0000000000..3a1c6c4fb3 --- /dev/null +++ b/playback/player/ios/GstPlay/OnlineMedia.plist @@ -0,0 +1,30 @@ + + + + + + http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi + http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov + http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg + http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi + + + http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv + http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.mp4 + http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.ogv + http://mirrorblender.top-ix.org/movies/sintel-1024-surround.mp4 + + + http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mkv + http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mov + http://media.xiph.org/mango/tears_of_steel_1080p.webm + + + http://radio.hbr1.com:19800/trance.ogg + http://radio.hbr1.com:19800/tronic.aac + + + http://non-existing.org/Non_Existing_Server + http://docs.gstreamer.com/Non_Existing_File + + From d270aec9d139c6201e405fac7b0a3781fdb460d3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 23 Apr 2015 13:02:34 +0100 Subject: [PATCH 051/412] playback/player: ios: Make sure dimensions are valid Layouting code crashes if 0/0 get passed https://github.com/sdroege/gst-player/pull/22 --- playback/player/ios/GstPlay/VideoViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index 150c72aae0..5c752f2063 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -159,7 +159,9 @@ static void video_dimensions_changed (GstPlayer * unused, gint width, gint height, VideoViewController * self) { dispatch_async(dispatch_get_main_queue(), ^{ - [self videoDimensionsChanged:width height:height]; + if (width > 0 && height > 0) { + [self videoDimensionsChanged:width height:height]; + } }); } From 7ca4b495c239228f8ce0c4fa1588000945c0e407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 25 Apr 2015 14:03:12 +0200 Subject: [PATCH 052/412] playback/player: gst-play: Fix compiler warning gst-play.c:89:11: error: comparison of unsigned expression >= 0 is always true [-Werror,-Wtautological-compare] if (pos >= 0 && dur > 0) { ~~~ ^ ~ --- playback/player/gst-play/gst-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 6fc73bbce3..9db6d5552a 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -86,7 +86,7 @@ position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) memset (status, ' ', sizeof (status) - 1); - if (pos >= 0 && dur > 0) { + if (pos != -1 && dur > 0 && dur != -1) { gchar dstr[32], pstr[32]; /* FIXME: pretty print in nicer format */ From 34c7b459066832e516933ec9168284fd5f4c8f4d Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 22 Apr 2015 17:39:50 -0500 Subject: [PATCH 053/412] playback/player: player: Add media information API https://github.com/sdroege/gst-player/pull/21 --- playback/player/gst-play/gst-play.c | 234 +++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 2 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 9db6d5552a..0b5c5b8481 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -110,6 +110,224 @@ buffering_cb (GstPlayer * player, gint percent, GstPlay * play) g_print ("Buffering: %d\n", percent); } +static void +print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) +{ + gint i, num; + + num = gst_tag_list_get_tag_size (list, tag); + for (i = 0; i < num; ++i) { + const GValue *val; + + val = gst_tag_list_get_value_index (list, tag, i); + if (G_VALUE_HOLDS_STRING (val)) { + g_print (" %s : %s \n", tag, g_value_get_string (val)); + } else if (G_VALUE_HOLDS_UINT (val)) { + g_print (" %s : %u \n", tag, g_value_get_uint (val)); + } else if (G_VALUE_HOLDS_DOUBLE (val)) { + g_print (" %s : %g \n", tag, g_value_get_double (val)); + } else if (G_VALUE_HOLDS_BOOLEAN (val)) { + g_print (" %s : %s \n", tag, + g_value_get_boolean (val) ? "true" : "false"); + } else if (GST_VALUE_HOLDS_DATE_TIME (val)) { + GstDateTime *dt = g_value_get_boxed (val); + gchar *dt_str = gst_date_time_to_iso8601_string (dt); + + g_print (" %s : %s \n", tag, dt_str); + g_free (dt_str); + } else { + g_print (" %s : tag of type '%s' \n", tag, G_VALUE_TYPE_NAME (val)); + } + } +} + +static void +print_video_info (GstPlayerVideoInfo * info) +{ + gint fps_n, fps_d; + guint par_n, par_d; + + if (info == NULL) + return; + + g_print (" width : %d\n", gst_player_video_info_get_width (info)); + g_print (" height : %d\n", gst_player_video_info_get_height (info)); + g_print (" max_bitrate : %d\n", + gst_player_video_info_get_max_bitrate (info)); + g_print (" bitrate : %d\n", gst_player_video_info_get_bitrate (info)); + gst_player_video_info_get_framerate (info, &fps_n, &fps_d); + g_print (" frameate : %.2f\n", (gdouble) fps_n / fps_d); + gst_player_video_info_get_pixel_aspect_ratio (info, &par_n, &par_d); + g_print (" pixel-aspect-ratio %u:%u\n", par_n, par_d); +} + +static void +print_audio_info (GstPlayerAudioInfo * info) +{ + if (info == NULL) + return; + + g_print (" sample rate : %d\n", + gst_player_audio_info_get_sample_rate (info)); + g_print (" channels : %d\n", gst_player_audio_info_get_channels (info)); + g_print (" max_bitrate : %d\n", + gst_player_audio_info_get_max_bitrate (info)); + g_print (" bitrate : %d\n", gst_player_audio_info_get_bitrate (info)); + g_print (" language : %s\n", gst_player_audio_info_get_language (info)); +} + +static void +print_subtitle_info (GstPlayerSubtitleInfo * info) +{ + if (info == NULL) + return; + + g_print (" language : %s\n", gst_player_subtitle_get_language (info)); +} + +static void +print_all_stream_info (GstPlayerMediaInfo * media_info) +{ + guint count = 0; + GList *l, *list; + + list = gst_player_media_info_get_stream_list (media_info); + g_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); + g_print ("Duration: %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); + for (l = list; l != NULL; l = l->next) { + GstTagList *tags = NULL; + GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; + + g_print (" Stream # %u \n", count++); + g_print (" type : %s_%u\n", + gst_player_stream_info_get_stream_type_nick (stream), + gst_player_stream_info_get_stream_index (stream)); + tags = gst_player_stream_info_get_stream_tags (stream); + g_print (" taglist : \n"); + if (tags) { + gst_tag_list_foreach (tags, print_one_tag, NULL); + } + + if (GST_IS_PLAYER_VIDEO_INFO (stream)) + print_video_info ((GstPlayerVideoInfo *) stream); + else if (GST_IS_PLAYER_AUDIO_INFO (stream)) + print_audio_info ((GstPlayerAudioInfo *) stream); + else + print_subtitle_info ((GstPlayerSubtitleInfo *) stream); + } +} + +static void +print_all_video_stream (GstPlayerMediaInfo * media_info) +{ + GList *list = NULL, *l; + + list = gst_player_get_video_streams (media_info); + if (!list) + return; + + g_print ("All video streams\n"); + for (l = list; l != NULL; l = l->next) { + GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) l->data; + GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), + gst_player_stream_info_get_stream_index (sinfo)); + print_video_info (info); + } +} + +static void +print_all_subtitle_stream (GstPlayerMediaInfo * media_info) +{ + GList *list = NULL, *l; + + list = gst_player_get_subtitle_streams (media_info); + if (!list) + return; + + g_print ("All subtitle streams:\n"); + for (l = list; l != NULL; l = l->next) { + GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) l->data; + GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), + gst_player_stream_info_get_stream_index (sinfo)); + print_subtitle_info (info); + } +} + +static void +print_all_audio_stream (GstPlayerMediaInfo * media_info) +{ + GList *list = NULL, *l; + + list = gst_player_get_audio_streams (media_info); + if (!list) + return; + + g_print ("All audio streams: \n"); + for (l = list; l != NULL; l = l->next) { + GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) l->data; + GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), + gst_player_stream_info_get_stream_index (sinfo)); + print_audio_info (info); + } +} + +static void +print_current_tracks (GstPlay * play) +{ + GstPlayerAudioInfo *audio = NULL; + GstPlayerVideoInfo *video = NULL; + GstPlayerSubtitleInfo *subtitle = NULL; + + g_print ("Current video track: \n"); + video = gst_player_get_current_video_track (play->player); + print_video_info (video); + + g_print ("Current audio track: \n"); + audio = gst_player_get_current_audio_track (play->player); + print_audio_info (audio); + + g_print ("Current subtitle track: \n"); + subtitle = gst_player_get_current_subtitle_track (play->player); + print_subtitle_info (subtitle); + + if (audio) + g_object_unref (audio); + + if (video) + g_object_unref (video); + + if (subtitle) + g_object_unref (subtitle); +} + +static void +print_media_info (GstPlayerMediaInfo * media_info) +{ + print_all_stream_info (media_info); + g_print ("\n"); + print_all_video_stream (media_info); + g_print ("\n"); + print_all_audio_stream (media_info); + g_print ("\n"); + print_all_subtitle_stream (media_info); +} + +static void +media_info_cb (GstPlayer * player, GstPlayerMediaInfo * info, GstPlay * play) +{ + static int once = 0; + + if (!once) { + print_media_info (info); + print_current_tracks (play); + once = 1; + } +} + static GstPlay * play_new (gchar ** uris, gdouble initial_volume) { @@ -128,12 +346,14 @@ play_new (gchar ** uris, gdouble initial_volume) G_CALLBACK (position_updated_cb), play); g_signal_connect (play->player, "state-changed", G_CALLBACK (state_changed_cb), play); - g_signal_connect (play->player, "buffering", - G_CALLBACK (buffering_cb), play); + g_signal_connect (play->player, "buffering", G_CALLBACK (buffering_cb), play); g_signal_connect (play->player, "end-of-stream", G_CALLBACK (end_of_stream_cb), play); g_signal_connect (play->player, "error", G_CALLBACK (error_cb), play); + g_signal_connect (play->player, "media-info-updated", + G_CALLBACK (media_info_cb), play); + play->loop = g_main_loop_new (NULL, FALSE); play->desired_state = GST_STATE_PLAYING; @@ -347,6 +567,16 @@ keyboard_cb (const gchar * key_input, gpointer user_data) GstPlay *play = (GstPlay *) user_data; switch (g_ascii_tolower (key_input[0])) { + case 'i': + { + GstPlayerMediaInfo *media_info = gst_player_get_media_info (play->player); + if (media_info) { + print_media_info (media_info); + g_object_unref (media_info); + print_current_tracks (play); + } + break; + } case ' ': toggle_paused (play); break; From da76b893c7f09aef7de03c9282c9a304d32157af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 25 Apr 2015 14:32:52 +0200 Subject: [PATCH 054/412] playback/player: gst-play: Add copyright notice for Brijesh Singh --- playback/player/gst-play/gst-play.c | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 0b5c5b8481..57e2ecf911 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -3,6 +3,7 @@ * Copyright (C) 2013-2014 Tim-Philipp Müller * Copyright (C) 2013 Collabora Ltd. * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public From 2cfa987d8981436ea43faafeccd49d0eb82952d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 25 Apr 2015 19:30:55 +0200 Subject: [PATCH 055/412] playback/player: player: Rename gst_player_subtitle_get_language() to gst_player_subtitle_info_get_language() --- playback/player/gst-play/gst-play.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 57e2ecf911..36f4889122 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -157,7 +157,7 @@ print_video_info (GstPlayerVideoInfo * info) gst_player_video_info_get_max_bitrate (info)); g_print (" bitrate : %d\n", gst_player_video_info_get_bitrate (info)); gst_player_video_info_get_framerate (info, &fps_n, &fps_d); - g_print (" frameate : %.2f\n", (gdouble) fps_n / fps_d); + g_print (" framerate : %.2f\n", (gdouble) fps_n / fps_d); gst_player_video_info_get_pixel_aspect_ratio (info, &par_n, &par_d); g_print (" pixel-aspect-ratio %u:%u\n", par_n, par_d); } @@ -183,7 +183,7 @@ print_subtitle_info (GstPlayerSubtitleInfo * info) if (info == NULL) return; - g_print (" language : %s\n", gst_player_subtitle_get_language (info)); + g_print (" language : %s\n", gst_player_subtitle_info_get_language (info)); } static void From 3180177fc7af7ed62c1aec2813daf164749fc3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 25 Apr 2015 19:37:39 +0200 Subject: [PATCH 056/412] playback/player: player: Rename some more functions for consistency --- playback/player/gst-play/gst-play.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 36f4889122..299f00f904 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -202,9 +202,9 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) g_print (" Stream # %u \n", count++); g_print (" type : %s_%u\n", - gst_player_stream_info_get_stream_type_nick (stream), - gst_player_stream_info_get_stream_index (stream)); - tags = gst_player_stream_info_get_stream_tags (stream); + gst_player_stream_info_get_stream_type (stream), + gst_player_stream_info_get_index (stream)); + tags = gst_player_stream_info_get_tags (stream); g_print (" taglist : \n"); if (tags) { gst_tag_list_foreach (tags, print_one_tag, NULL); @@ -232,8 +232,8 @@ print_all_video_stream (GstPlayerMediaInfo * media_info) for (l = list; l != NULL; l = l->next) { GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), - gst_player_stream_info_get_stream_index (sinfo)); + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_player_stream_info_get_index (sinfo)); print_video_info (info); } } @@ -251,8 +251,8 @@ print_all_subtitle_stream (GstPlayerMediaInfo * media_info) for (l = list; l != NULL; l = l->next) { GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), - gst_player_stream_info_get_stream_index (sinfo)); + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_player_stream_info_get_index (sinfo)); print_subtitle_info (info); } } @@ -270,8 +270,8 @@ print_all_audio_stream (GstPlayerMediaInfo * media_info) for (l = list; l != NULL; l = l->next) { GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type_nick (sinfo), - gst_player_stream_info_get_stream_index (sinfo)); + g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_player_stream_info_get_index (sinfo)); print_audio_info (info); } } From 81238fdbcd73026497fe3ffd8bbf0c71c78288df Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Sun, 26 Apr 2015 11:01:44 -0500 Subject: [PATCH 057/412] playback/player: gtk-play: add media information window --- playback/player/gtk/gtk-play.c | 359 ++++++++++++++++++++++++++++++++- 1 file changed, 357 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index e3ebea10f8..8705f9660e 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1,6 +1,7 @@ /* GStreamer * * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -51,10 +52,37 @@ typedef struct GtkWidget *seekbar; GtkWidget *video_area; GtkWidget *volume_button; + GtkWidget *media_info; gulong seekbar_value_changed_signal_id; gboolean playing; } GtkPlay; +enum { + COL_TEXT = 0, + COL_NUM +}; + +enum { + VIDEO_INFO_START, + VIDEO_INFO_RESOLUTION, + VIDEO_INFO_FPS, + VIDEO_INFO_PAR, + VIDEO_INFO_CODEC, + VIDEO_INFO_MAX_BITRATE, + VIDEO_INFO_END, + AUDIO_INFO_START, + AUDIO_INFO_CHANNELS, + AUDIO_INFO_RATE, + AUDIO_INFO_LANGUAGE, + AUDIO_INFO_CODEC, + AUDIO_INFO_MAX_BITRATE, + AUDIO_INFO_END, + SUBTITLE_INFO_START, + SUBTITLE_INFO_LANGUAGE, + SUBTITLE_INFO_CODEC, + SUBTITLE_INFO_END, +}; + static void set_title (GtkPlay * play, const gchar * title) { @@ -133,6 +161,7 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) g_return_if_fail (prev != NULL); gtk_widget_set_sensitive (play->next_button, TRUE); + gtk_widget_set_sensitive (play->media_info, FALSE); gst_player_set_uri (play->player, prev->data); gst_player_play (play->player); set_title (play, prev->data); @@ -153,12 +182,318 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) g_return_if_fail (next != NULL); gtk_widget_set_sensitive (play->prev_button, TRUE); + gtk_widget_set_sensitive (play->media_info, FALSE); gst_player_set_uri (play->player, next->data); gst_player_play (play->player); set_title (play, next->data); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); } +static gchar* +stream_info_get_string (GstPlayerStreamInfo *stream, gint type, + gboolean label) +{ + switch (type) { + case AUDIO_INFO_RATE: + { + gchar *buffer; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + buffer = g_strdup_printf ("%s%d", label ? "Sample rate : " : "", + gst_player_audio_info_get_sample_rate (audio)); + return buffer; + } + case AUDIO_INFO_LANGUAGE: + { + gchar *buffer; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", + gst_player_audio_info_get_language (audio)); + return buffer; + } + case AUDIO_INFO_CHANNELS: + { + gchar *buffer; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + buffer = g_strdup_printf ("%s%d", label ? "Channels : " : "", + gst_player_audio_info_get_channels (audio)); + return buffer; + } + case SUBTITLE_INFO_CODEC: + case VIDEO_INFO_CODEC: + case AUDIO_INFO_CODEC: + { + gchar *buffer; + buffer = g_strdup_printf ("%s%s", label ? "Codec : " : "", + gst_player_stream_info_get_codec (stream)); + return buffer; + } + case AUDIO_INFO_MAX_BITRATE: + { + gchar *buffer = NULL; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + gint bitrate = gst_player_audio_info_get_max_bitrate (audio); + + if (bitrate > 0) + buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", + bitrate); + return buffer; + } + case VIDEO_INFO_MAX_BITRATE: + { + gchar *buffer = NULL; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + gint bitrate = gst_player_video_info_get_max_bitrate (video); + + if (bitrate > 0) + buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", + bitrate); + return buffer; + } + case VIDEO_INFO_PAR: + { + gint par_d, par_n; + gchar *buffer; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + + gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); + buffer = g_strdup_printf ("%s%d:%d", label ? "pixel-aspect-ratio : " : + "", par_n, par_d); + return buffer; + } + case VIDEO_INFO_FPS: + { + gint fps_d, fps_n; + gchar *buffer; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + + gst_player_video_info_get_framerate (video, &fps_n, &fps_d); + buffer = g_strdup_printf ("%s%.2f", label ? "Framerate : " : "", + (gdouble)fps_n/fps_d); + return buffer; + } + case VIDEO_INFO_RESOLUTION: + { + gchar *buffer; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + buffer = g_strdup_printf ("%s%dx%d", label ? "Resolution : " : "", + gst_player_video_info_get_width (video), + gst_player_video_info_get_height (video)); + return buffer; + } + case SUBTITLE_INFO_LANGUAGE: + { + gchar *buffer; + GstPlayerSubtitleInfo *sub = (GstPlayerSubtitleInfo*)stream; + buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", + gst_player_subtitle_info_get_language (sub)); + return buffer; + } + default: + { + return NULL; + } + } +} + +static gboolean +is_current_stream (GtkPlay *play, GstPlayerStreamInfo *stream) +{ + gboolean ret = FALSE; + GstPlayerStreamInfo *s; + GstPlayerVideoInfo *video = gst_player_get_current_video_track(play->player); + GstPlayerAudioInfo *audio = gst_player_get_current_audio_track(play->player); + GstPlayerSubtitleInfo *sub = gst_player_get_current_subtitle_track(play->player); + + if (GST_IS_PLAYER_VIDEO_INFO(stream)) + s = (GstPlayerStreamInfo*) video; + else if (GST_IS_PLAYER_AUDIO_INFO(stream)) + s = (GstPlayerStreamInfo*) audio; + else + s = (GstPlayerStreamInfo*) sub; + + if (s) + if (gst_player_stream_info_get_index (stream) == + gst_player_stream_info_get_index (s)) + ret = TRUE; + + if (audio) + g_object_unref (audio); + + if (video) + g_object_unref (video); + + if (sub) + g_object_unref (sub); + + return ret; +} + +static GtkTreeModel* +create_and_fill_model (GtkPlay *play, GstPlayerMediaInfo *info) +{ + GList *l; + guint count; + GtkTreeStore *tree; + GtkTreeIter child, parent; + + count = 0; + tree = gtk_tree_store_new (COL_NUM, G_TYPE_STRING); + + for (l = gst_player_media_info_get_stream_list(info); + l != NULL; l = l->next) { + gchar *buffer; + gint i, start, end; + GstPlayerStreamInfo *stream = (GstPlayerStreamInfo*) l->data; + + /* define the field range based on stream type */ + if (GST_IS_PLAYER_VIDEO_INFO(stream)) { + start = VIDEO_INFO_START + 1; + end = VIDEO_INFO_END; + } else if (GST_IS_PLAYER_AUDIO_INFO(stream)) { + start = AUDIO_INFO_START + 1; + end = AUDIO_INFO_END; + } else { + start = SUBTITLE_INFO_START + 1; + end = SUBTITLE_INFO_END; + } + + buffer = g_strdup_printf ("Stream %u %s", count++, + is_current_stream (play, stream) ? "(current)" : ""); + gtk_tree_store_append (tree, &parent, NULL); + gtk_tree_store_set (tree, &parent, COL_TEXT, buffer, -1); + g_free (buffer); + + buffer = g_strdup_printf ("Type : %s", + gst_player_stream_info_get_stream_type (stream)); + gtk_tree_store_append (tree, &child, &parent ); + gtk_tree_store_set (tree, &child, COL_TEXT, buffer, -1); + g_free (buffer); + + for (i = start; i < end; i++) { + buffer = stream_info_get_string (stream, i, TRUE); + if (buffer) { + gtk_tree_store_append (tree, &child, &parent); + gtk_tree_store_set (tree, &child, COL_TEXT, buffer, -1); + g_free (buffer); + } + } + } + + return GTK_TREE_MODEL(tree); +} + +static GtkWidget* +create_view_and_model (GtkPlay *play, GstPlayerMediaInfo *info) +{ + GtkWidget *view; + GtkTreeModel *model; + GtkTreeViewColumn *col; + GtkCellRenderer *renderer; + + view = gtk_tree_view_new (); + col = gtk_tree_view_column_new (); + gtk_tree_view_append_column (GTK_TREE_VIEW(view), col); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(view), FALSE); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, renderer, TRUE); + gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT); + + model = create_and_fill_model (play, info); + gtk_tree_view_set_model (GTK_TREE_VIEW(view), model); + g_object_unref (model); + + return view; +} + +static void +delete_media_info_window (GtkWidget *button, GtkWindow *window) +{ + gtk_window_close (window); +} + +static void +create_media_info_window (GtkPlay *play, GstPlayerMediaInfo *info) +{ + GtkWidget *sw; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *view; + GtkWidget *hbox; + GtkWidget *uri; + GtkWidget *loc; + GtkTextIter iter; + GtkWidget *window; + GtkTextBuffer *buffer; + GtkWidget *hbox_close; + GtkWidget *button_close; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW(window), "Media information"); + gtk_window_set_default_size (GTK_WINDOW(window), 550, 450); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_container_set_border_width (GTK_CONTAINER(window), 10); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add (GTK_CONTAINER(window), vbox); + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), + "Information about all the streams contains in your media. \n" + "Current selected streams are marked as (current)."); + gtk_label_set_justify (GTK_LABEL(label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 2); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + GTK_SHADOW_ETCHED_IN); + gtk_box_pack_start (GTK_BOX(vbox), sw, TRUE, TRUE, 0); + + view = create_view_and_model (play, info); + gtk_container_add (GTK_CONTAINER(sw), view); + g_signal_connect (view, "realize", + G_CALLBACK(gtk_tree_view_expand_all), NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 2); + + loc = gtk_label_new ("Location : "); + gtk_box_pack_start (GTK_BOX(hbox), loc, FALSE, FALSE, 2); + + buffer = gtk_text_buffer_new (NULL); + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, + gst_player_media_info_get_uri (info), -1); + uri = gtk_text_view_new_with_buffer (buffer); + gtk_box_pack_start (GTK_BOX(hbox), uri, FALSE, FALSE, 2); + gtk_text_view_set_editable (GTK_TEXT_VIEW(uri), FALSE); + g_object_unref (buffer); + + hbox_close = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); + gtk_box_pack_start (GTK_BOX(vbox), hbox_close, FALSE, FALSE, 2); + button_close = gtk_button_new_with_label (" Close "); + g_signal_connect (button_close, "clicked", + G_CALLBACK(delete_media_info_window), window); + gtk_box_pack_end (GTK_BOX(hbox_close), button_close, FALSE, FALSE, 3); + + gtk_widget_show_all (window); +} + +static void +media_info_clicked_cb (GtkButton *button, GtkPlay *play) +{ + GstPlayerMediaInfo *info; + + info = gst_player_get_media_info (play->player); + if (!info) + return; + + create_media_info_window (play, info); + + g_object_unref (info); +} + static void seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) { @@ -224,13 +559,21 @@ create_ui (GtkPlay * play) g_signal_connect (G_OBJECT (play->volume_button), "value-changed", G_CALLBACK (volume_changed_cb), play); + /* media information button */ + play->media_info = gtk_button_new_from_icon_name ("dialog-information", + GTK_ICON_SIZE_BUTTON); + g_signal_connect (G_OBJECT (play->media_info), "clicked", + G_CALLBACK (media_info_clicked_cb), play); + gtk_widget_set_sensitive (play->media_info, FALSE); + controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, - 2); + gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, + FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->media_info, FALSE, FALSE, 2); main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); @@ -300,6 +643,8 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); + gtk_widget_set_sensitive (play->media_info, FALSE); + gst_player_set_uri (play->player, next->data); gst_player_play (play->player); set_title (play, next->data); @@ -316,6 +661,14 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } } +static void +media_info_updated_cb (GstPlayer *player, GstPlayerMediaInfo *media_info, + GtkPlay *play) +{ + if (!gtk_widget_is_sensitive (play->media_info)) + gtk_widget_set_sensitive (play->media_info, TRUE); +} + int main (gint argc, gchar ** argv) { @@ -403,6 +756,8 @@ main (gint argc, gchar ** argv) g_signal_connect (play.player, "video-dimensions-changed", G_CALLBACK (video_dimensions_changed_cb), &play); g_signal_connect (play.player, "end-of-stream", G_CALLBACK (eos_cb), &play); + g_signal_connect (play.player, "media-info-updated", + G_CALLBACK (media_info_updated_cb), &play); /* We have file(s) that need playing. */ set_title (&play, g_list_first (play.uris)->data); From bf8604dcf352948e3b6926290539cc716ff0d263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 26 Apr 2015 18:31:30 +0200 Subject: [PATCH 058/412] playback/player: player/gtk: Fix indention --- playback/player/gtk/gtk-play.c | 153 +++++++++++++++++---------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8705f9660e..c14967ff28 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -57,12 +57,14 @@ typedef struct gboolean playing; } GtkPlay; -enum { +enum +{ COL_TEXT = 0, COL_NUM }; -enum { +enum +{ VIDEO_INFO_START, VIDEO_INFO_RESOLUTION, VIDEO_INFO_FPS, @@ -189,33 +191,32 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); } -static gchar* -stream_info_get_string (GstPlayerStreamInfo *stream, gint type, - gboolean label) +static gchar * +stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { switch (type) { case AUDIO_INFO_RATE: { gchar *buffer; - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; buffer = g_strdup_printf ("%s%d", label ? "Sample rate : " : "", - gst_player_audio_info_get_sample_rate (audio)); + gst_player_audio_info_get_sample_rate (audio)); return buffer; } case AUDIO_INFO_LANGUAGE: { gchar *buffer; - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", - gst_player_audio_info_get_language (audio)); + gst_player_audio_info_get_language (audio)); return buffer; } case AUDIO_INFO_CHANNELS: { gchar *buffer; - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; buffer = g_strdup_printf ("%s%d", label ? "Channels : " : "", - gst_player_audio_info_get_channels (audio)); + gst_player_audio_info_get_channels (audio)); return buffer; } case SUBTITLE_INFO_CODEC: @@ -224,13 +225,13 @@ stream_info_get_string (GstPlayerStreamInfo *stream, gint type, { gchar *buffer; buffer = g_strdup_printf ("%s%s", label ? "Codec : " : "", - gst_player_stream_info_get_codec (stream)); + gst_player_stream_info_get_codec (stream)); return buffer; } case AUDIO_INFO_MAX_BITRATE: { gchar *buffer = NULL; - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo*)stream; + GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; gint bitrate = gst_player_audio_info_get_max_bitrate (audio); if (bitrate > 0) @@ -241,7 +242,7 @@ stream_info_get_string (GstPlayerStreamInfo *stream, gint type, case VIDEO_INFO_MAX_BITRATE: { gchar *buffer = NULL; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gint bitrate = gst_player_video_info_get_max_bitrate (video); if (bitrate > 0) @@ -253,7 +254,7 @@ stream_info_get_string (GstPlayerStreamInfo *stream, gint type, { gint par_d, par_n; gchar *buffer; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); buffer = g_strdup_printf ("%s%d:%d", label ? "pixel-aspect-ratio : " : @@ -264,28 +265,28 @@ stream_info_get_string (GstPlayerStreamInfo *stream, gint type, { gint fps_d, fps_n; gchar *buffer; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gst_player_video_info_get_framerate (video, &fps_n, &fps_d); buffer = g_strdup_printf ("%s%.2f", label ? "Framerate : " : "", - (gdouble)fps_n/fps_d); + (gdouble) fps_n / fps_d); return buffer; } case VIDEO_INFO_RESOLUTION: { gchar *buffer; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo*)stream; + GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; buffer = g_strdup_printf ("%s%dx%d", label ? "Resolution : " : "", - gst_player_video_info_get_width (video), - gst_player_video_info_get_height (video)); + gst_player_video_info_get_width (video), + gst_player_video_info_get_height (video)); return buffer; } case SUBTITLE_INFO_LANGUAGE: { gchar *buffer; - GstPlayerSubtitleInfo *sub = (GstPlayerSubtitleInfo*)stream; + GstPlayerSubtitleInfo *sub = (GstPlayerSubtitleInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", - gst_player_subtitle_info_get_language (sub)); + gst_player_subtitle_info_get_language (sub)); return buffer; } default: @@ -296,20 +297,21 @@ stream_info_get_string (GstPlayerStreamInfo *stream, gint type, } static gboolean -is_current_stream (GtkPlay *play, GstPlayerStreamInfo *stream) +is_current_stream (GtkPlay * play, GstPlayerStreamInfo * stream) { gboolean ret = FALSE; GstPlayerStreamInfo *s; - GstPlayerVideoInfo *video = gst_player_get_current_video_track(play->player); - GstPlayerAudioInfo *audio = gst_player_get_current_audio_track(play->player); - GstPlayerSubtitleInfo *sub = gst_player_get_current_subtitle_track(play->player); - - if (GST_IS_PLAYER_VIDEO_INFO(stream)) - s = (GstPlayerStreamInfo*) video; - else if (GST_IS_PLAYER_AUDIO_INFO(stream)) - s = (GstPlayerStreamInfo*) audio; + GstPlayerVideoInfo *video = gst_player_get_current_video_track (play->player); + GstPlayerAudioInfo *audio = gst_player_get_current_audio_track (play->player); + GstPlayerSubtitleInfo *sub = + gst_player_get_current_subtitle_track (play->player); + + if (GST_IS_PLAYER_VIDEO_INFO (stream)) + s = (GstPlayerStreamInfo *) video; + else if (GST_IS_PLAYER_AUDIO_INFO (stream)) + s = (GstPlayerStreamInfo *) audio; else - s = (GstPlayerStreamInfo*) sub; + s = (GstPlayerStreamInfo *) sub; if (s) if (gst_player_stream_info_get_index (stream) == @@ -328,8 +330,8 @@ is_current_stream (GtkPlay *play, GstPlayerStreamInfo *stream) return ret; } -static GtkTreeModel* -create_and_fill_model (GtkPlay *play, GstPlayerMediaInfo *info) +static GtkTreeModel * +create_and_fill_model (GtkPlay * play, GstPlayerMediaInfo * info) { GList *l; guint count; @@ -339,17 +341,16 @@ create_and_fill_model (GtkPlay *play, GstPlayerMediaInfo *info) count = 0; tree = gtk_tree_store_new (COL_NUM, G_TYPE_STRING); - for (l = gst_player_media_info_get_stream_list(info); - l != NULL; l = l->next) { + for (l = gst_player_media_info_get_stream_list (info); l != NULL; l = l->next) { gchar *buffer; gint i, start, end; - GstPlayerStreamInfo *stream = (GstPlayerStreamInfo*) l->data; + GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; /* define the field range based on stream type */ - if (GST_IS_PLAYER_VIDEO_INFO(stream)) { + if (GST_IS_PLAYER_VIDEO_INFO (stream)) { start = VIDEO_INFO_START + 1; end = VIDEO_INFO_END; - } else if (GST_IS_PLAYER_AUDIO_INFO(stream)) { + } else if (GST_IS_PLAYER_AUDIO_INFO (stream)) { start = AUDIO_INFO_START + 1; end = AUDIO_INFO_END; } else { @@ -364,8 +365,8 @@ create_and_fill_model (GtkPlay *play, GstPlayerMediaInfo *info) g_free (buffer); buffer = g_strdup_printf ("Type : %s", - gst_player_stream_info_get_stream_type (stream)); - gtk_tree_store_append (tree, &child, &parent ); + gst_player_stream_info_get_stream_type (stream)); + gtk_tree_store_append (tree, &child, &parent); gtk_tree_store_set (tree, &child, COL_TEXT, buffer, -1); g_free (buffer); @@ -379,11 +380,11 @@ create_and_fill_model (GtkPlay *play, GstPlayerMediaInfo *info) } } - return GTK_TREE_MODEL(tree); + return GTK_TREE_MODEL (tree); } -static GtkWidget* -create_view_and_model (GtkPlay *play, GstPlayerMediaInfo *info) +static GtkWidget * +create_view_and_model (GtkPlay * play, GstPlayerMediaInfo * info) { GtkWidget *view; GtkTreeModel *model; @@ -392,28 +393,28 @@ create_view_and_model (GtkPlay *play, GstPlayerMediaInfo *info) view = gtk_tree_view_new (); col = gtk_tree_view_column_new (); - gtk_tree_view_append_column (GTK_TREE_VIEW(view), col); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(view), FALSE); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), col); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, renderer, TRUE); gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT); model = create_and_fill_model (play, info); - gtk_tree_view_set_model (GTK_TREE_VIEW(view), model); + gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); g_object_unref (model); return view; } static void -delete_media_info_window (GtkWidget *button, GtkWindow *window) +delete_media_info_window (GtkWidget * button, GtkWindow * window) { gtk_window_close (window); } static void -create_media_info_window (GtkPlay *play, GstPlayerMediaInfo *info) +create_media_info_window (GtkPlay * play, GstPlayerMediaInfo * info) { GtkWidget *sw; GtkWidget *vbox; @@ -429,61 +430,61 @@ create_media_info_window (GtkPlay *play, GstPlayerMediaInfo *info) GtkWidget *button_close; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW(window), "Media information"); - gtk_window_set_default_size (GTK_WINDOW(window), 550, 450); - gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); - gtk_container_set_border_width (GTK_CONTAINER(window), 10); + gtk_window_set_title (GTK_WINDOW (window), "Media information"); + gtk_window_set_default_size (GTK_WINDOW (window), 550, 450); + gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); + gtk_container_set_border_width (GTK_CONTAINER (window), 10); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); - gtk_container_add (GTK_CONTAINER(window), vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL(label), - "Information about all the streams contains in your media. \n" - "Current selected streams are marked as (current)."); - gtk_label_set_justify (GTK_LABEL(label), GTK_JUSTIFY_LEFT); - gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5); - gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 2); + gtk_label_set_markup (GTK_LABEL (label), + "Information about all the streams contains in your media. \n" + "Current selected streams are marked as (current)."); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 2); sw = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN); - gtk_box_pack_start (GTK_BOX(vbox), sw, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); view = create_view_and_model (play, info); - gtk_container_add (GTK_CONTAINER(sw), view); + gtk_container_add (GTK_CONTAINER (sw), view); g_signal_connect (view, "realize", - G_CALLBACK(gtk_tree_view_expand_all), NULL); + G_CALLBACK (gtk_tree_view_expand_all), NULL); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); - gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); loc = gtk_label_new ("Location : "); - gtk_box_pack_start (GTK_BOX(hbox), loc, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (hbox), loc, FALSE, FALSE, 2); buffer = gtk_text_buffer_new (NULL); gtk_text_buffer_get_start_iter (buffer, &iter); gtk_text_buffer_insert (buffer, &iter, gst_player_media_info_get_uri (info), -1); uri = gtk_text_view_new_with_buffer (buffer); - gtk_box_pack_start (GTK_BOX(hbox), uri, FALSE, FALSE, 2); - gtk_text_view_set_editable (GTK_TEXT_VIEW(uri), FALSE); + gtk_box_pack_start (GTK_BOX (hbox), uri, FALSE, FALSE, 2); + gtk_text_view_set_editable (GTK_TEXT_VIEW (uri), FALSE); g_object_unref (buffer); hbox_close = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); - gtk_box_pack_start (GTK_BOX(vbox), hbox_close, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox_close, FALSE, FALSE, 2); button_close = gtk_button_new_with_label (" Close "); g_signal_connect (button_close, "clicked", - G_CALLBACK(delete_media_info_window), window); - gtk_box_pack_end (GTK_BOX(hbox_close), button_close, FALSE, FALSE, 3); + G_CALLBACK (delete_media_info_window), window); + gtk_box_pack_end (GTK_BOX (hbox_close), button_close, FALSE, FALSE, 3); gtk_widget_show_all (window); } static void -media_info_clicked_cb (GtkButton *button, GtkPlay *play) +media_info_clicked_cb (GtkButton * button, GtkPlay * play) { - GstPlayerMediaInfo *info; + GstPlayerMediaInfo *info; info = gst_player_get_media_info (play->player); if (!info) @@ -561,7 +562,7 @@ create_ui (GtkPlay * play) /* media information button */ play->media_info = gtk_button_new_from_icon_name ("dialog-information", - GTK_ICON_SIZE_BUTTON); + GTK_ICON_SIZE_BUTTON); g_signal_connect (G_OBJECT (play->media_info), "clicked", G_CALLBACK (media_info_clicked_cb), play); gtk_widget_set_sensitive (play->media_info, FALSE); @@ -662,8 +663,8 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } static void -media_info_updated_cb (GstPlayer *player, GstPlayerMediaInfo *media_info, - GtkPlay *play) +media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, + GtkPlay * play) { if (!gtk_widget_is_sensitive (play->media_info)) gtk_widget_set_sensitive (play->media_info, TRUE); From 2a5ba6574f5f2bab44448c54cc1820dd0cbd5ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 4 May 2015 22:37:34 +0200 Subject: [PATCH 059/412] playback/player: android: Add gstplayer-media-info.c to the build JNI bindings still need updating for the new media info API. https://github.com/sdroege/gst-player/issues/30 --- playback/player/android/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 439baa9bcd..19250098ce 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -3,7 +3,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c +LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c ../../lib/gst/player/gstplayer-media-info.c LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) From 58a2c722d9c833aa2c583572f5a269a027f0fc8a Mon Sep 17 00:00:00 2001 From: luis Date: Tue, 5 May 2015 13:45:49 +0200 Subject: [PATCH 060/412] network/http-launch: Make GList clients consistent (items valid always). --- network/http-launch/http-launch.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 38b4400627..2521f3b00c 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -54,6 +54,10 @@ remove_client (Client * client) { g_print ("Removing connection %s\n", client->name); + G_LOCK (clients); + clients = g_list_remove (clients, client); + G_UNLOCK (clients); + g_free (client->name); if (client->isource) { @@ -67,10 +71,6 @@ remove_client (Client * client) g_object_unref (client->connection); g_byte_array_unref (client->current_message); - G_LOCK (clients); - clients = g_list_remove (clients, client); - G_UNLOCK (clients); - g_slice_free (Client, client); } From c3928830cf3a3219ab351b7ca7576ff50262f34d Mon Sep 17 00:00:00 2001 From: Alex Moreno Date: Wed, 6 May 2015 10:33:53 +0800 Subject: [PATCH 061/412] playback/player: android: remove unused variable 'gst_app_thread' --- playback/player/android/jni/player.c | 1 - 1 file changed, 1 deletion(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index 577fab9d3b..ebfd0342e4 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -40,7 +40,6 @@ typedef struct _Player ANativeWindow *native_window; } Player; -static pthread_t gst_app_thread; static pthread_key_t current_jni_env; static JavaVM *java_vm; static jfieldID native_player_field_id; From 261081aade429107f76755788eab8abd44cab796 Mon Sep 17 00:00:00 2001 From: Alex Moreno Date: Wed, 6 May 2015 10:38:25 +0800 Subject: [PATCH 062/412] playback/player: android: check return values from JNI methods in 'JNI_OnLoad' --- playback/player/android/jni/player.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index ebfd0342e4..f9fcaeab9a 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -474,8 +474,17 @@ JNI_OnLoad (JavaVM * vm, void *reserved) return 0; } jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/Player"); - (*env)->RegisterNatives (env, klass, native_methods, - G_N_ELEMENTS (native_methods)); + if (!klass) { + __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", + "Could not retrieve class org.freedesktop.gstreamer.Player"); + return 0; + } + if ((*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods))) { + __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", + "Could not register native methods for org.freedesktop.gstreamer.Player"); + return 0; + } pthread_key_create (¤t_jni_env, detach_current_thread); From 696e1c9ab834f00128d0de7260eb8b3b69ef76fd Mon Sep 17 00:00:00 2001 From: Alex Moreno Date: Wed, 6 May 2015 10:52:26 +0800 Subject: [PATCH 063/412] playback/player: android: fixed compilation error --- playback/player/android/jni/Android.mk | 1 + playback/player/android/jni/player.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 19250098ce..fe4d50aaa8 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := gstplayer LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c ../../lib/gst/player/gstplayer-media-info.c +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../lib LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index f9fcaeab9a..8670b17d61 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "../../lib/gst/player/gstplayer.h" +#include "gst/player/gstplayer.h" GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category From ef751e3dfdf138d84c05d775a057bce4bc9678ac Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 29 Apr 2015 21:56:17 -0500 Subject: [PATCH 064/412] playback/player: gst-play: display global taglist --- playback/player/gst-play/gst-play.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 299f00f904..ae65092d5c 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -196,6 +196,12 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) g_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); g_print ("Duration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); + g_print ("Global taglist:\n"); + if (gst_player_media_info_get_tags (media_info)) + gst_tag_list_foreach (gst_player_media_info_get_tags (media_info), + print_one_tag, NULL); + else + g_print (" (nil) \n"); for (l = list; l != NULL; l = l->next) { GstTagList *tags = NULL; GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; From 544c51fe11fe7f4d57c5fccf2466864430f179c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 6 May 2015 10:58:27 +0200 Subject: [PATCH 065/412] playback/player: Fix indention --- playback/player/gst-play/gst-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index ae65092d5c..02a36f5824 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -199,7 +199,7 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) g_print ("Global taglist:\n"); if (gst_player_media_info_get_tags (media_info)) gst_tag_list_foreach (gst_player_media_info_get_tags (media_info), - print_one_tag, NULL); + print_one_tag, NULL); else g_print (" (nil) \n"); for (l = list; l != NULL; l = l->next) { From 611f3cde4b5619e178fad1bb4ab9b51eff1a484b Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 6 May 2015 06:41:06 -0500 Subject: [PATCH 066/412] playback/player: gtk-play: remove gtk deprecated API. --- playback/player/gtk/gtk-play.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index c14967ff28..246adeab16 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -443,7 +443,6 @@ create_media_info_window (GtkPlay * play, GstPlayerMediaInfo * info) "Information about all the streams contains in your media. \n" "Current selected streams are marked as (current)."); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); - gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 2); sw = gtk_scrolled_window_new (NULL, NULL); @@ -519,7 +518,6 @@ create_ui (GtkPlay * play) set_title (play, APP_NAME); play->video_area = gtk_drawing_area_new (); - gtk_widget_set_double_buffered (play->video_area, FALSE); g_signal_connect (play->video_area, "realize", G_CALLBACK (video_area_realize_cb), play); From 88e7131ae7d060e2aefd6a619fd854e6d0920ef2 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 6 May 2015 06:45:23 -0500 Subject: [PATCH 067/412] playback/player: gtk-play: set window title from media information If media contains title then use it to set player window title. --- playback/player/gtk/gtk-play.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 246adeab16..961fda176c 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -664,8 +664,15 @@ static void media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, GtkPlay * play) { - if (!gtk_widget_is_sensitive (play->media_info)) + if (!gtk_widget_is_sensitive (play->media_info)) { + const gchar *title; + + title = gst_player_media_info_get_title (media_info); + if (title) + set_title (play, title); + gtk_widget_set_sensitive (play->media_info, TRUE); + } } int From 653c8c973e6933dad9adc4ab0872e422d587bb2c Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 6 May 2015 06:54:48 -0500 Subject: [PATCH 068/412] playback/player: gtk-play: handle duplicate uri in playlist current logic does not play all the files from playlist if the list contains a duplicate uris. --- playback/player/gtk/gtk-play.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 961fda176c..b6ff4031a4 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -45,6 +45,7 @@ typedef struct gchar *uri; GList *uris; + GList *current_uri; GtkWidget *window; GtkWidget *play_pause_button; @@ -155,16 +156,13 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) GList *prev; gchar *cur_uri; - prev = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), (GCompareFunc) strcmp); - - g_return_if_fail (prev != NULL); - prev = g_list_previous (prev); + prev = g_list_previous (play->current_uri); g_return_if_fail (prev != NULL); gtk_widget_set_sensitive (play->next_button, TRUE); gtk_widget_set_sensitive (play->media_info, FALSE); gst_player_set_uri (play->player, prev->data); + play->current_uri = prev; gst_player_play (play->player); set_title (play, prev->data); gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); @@ -176,16 +174,13 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) GList *next, *l; gchar *cur_uri; - next = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), (GCompareFunc) strcmp); - - g_return_if_fail (next != NULL); - next = g_list_next (next); + next = g_list_next (play->current_uri); g_return_if_fail (next != NULL); gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->media_info, FALSE); gst_player_set_uri (play->player, next->data); + play->current_uri = next; gst_player_play (play->player); set_title (play, next->data); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); @@ -631,12 +626,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) GList *next = NULL; gchar *uri; - next = g_list_find_custom (play->uris, - gst_player_get_uri (play->player), (GCompareFunc) strcmp); - - g_return_if_fail (next != NULL); - - next = g_list_next (next); + next = g_list_next (play->current_uri); if (next) { if (!gtk_widget_is_sensitive (play->prev_button)) gtk_widget_set_sensitive (play->prev_button, TRUE); @@ -645,6 +635,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gtk_widget_set_sensitive (play->media_info, FALSE); gst_player_set_uri (play->player, next->data); + play->current_uri = next; gst_player_play (play->player); set_title (play, next->data); } else { @@ -768,6 +759,7 @@ main (gint argc, gchar ** argv) /* We have file(s) that need playing. */ set_title (&play, g_list_first (play.uris)->data); gst_player_play (play.player); + play.current_uri = g_list_first (play.uris); gtk_main (); From bbc6f056b13e4cd3f7f7eabe9b11d5a2b25e88a3 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 6 May 2015 07:04:28 -0500 Subject: [PATCH 069/412] playback/player: gtk-play: variable rename for consistency rename media_info varible holding button state to be consistent with others. --- playback/player/gtk/gtk-play.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index b6ff4031a4..8f6a77cf25 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -53,7 +53,7 @@ typedef struct GtkWidget *seekbar; GtkWidget *video_area; GtkWidget *volume_button; - GtkWidget *media_info; + GtkWidget *media_info_button; gulong seekbar_value_changed_signal_id; gboolean playing; } GtkPlay; @@ -160,7 +160,7 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) g_return_if_fail (prev != NULL); gtk_widget_set_sensitive (play->next_button, TRUE); - gtk_widget_set_sensitive (play->media_info, FALSE); + gtk_widget_set_sensitive (play->media_info_button, FALSE); gst_player_set_uri (play->player, prev->data); play->current_uri = prev; gst_player_play (play->player); @@ -178,7 +178,7 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) g_return_if_fail (next != NULL); gtk_widget_set_sensitive (play->prev_button, TRUE); - gtk_widget_set_sensitive (play->media_info, FALSE); + gtk_widget_set_sensitive (play->media_info_button, FALSE); gst_player_set_uri (play->player, next->data); play->current_uri = next; gst_player_play (play->player); @@ -554,11 +554,11 @@ create_ui (GtkPlay * play) G_CALLBACK (volume_changed_cb), play); /* media information button */ - play->media_info = gtk_button_new_from_icon_name ("dialog-information", + play->media_info_button = gtk_button_new_from_icon_name ("dialog-information", GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->media_info), "clicked", + g_signal_connect (G_OBJECT (play->media_info_button), "clicked", G_CALLBACK (media_info_clicked_cb), play); - gtk_widget_set_sensitive (play->media_info, FALSE); + gtk_widget_set_sensitive (play->media_info_button, FALSE); controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); @@ -567,7 +567,8 @@ create_ui (GtkPlay * play) gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->media_info, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->media_info_button, + FALSE, FALSE, 2); main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); @@ -632,7 +633,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); - gtk_widget_set_sensitive (play->media_info, FALSE); + gtk_widget_set_sensitive (play->media_info_button, FALSE); gst_player_set_uri (play->player, next->data); play->current_uri = next; @@ -655,14 +656,14 @@ static void media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, GtkPlay * play) { - if (!gtk_widget_is_sensitive (play->media_info)) { + if (!gtk_widget_is_sensitive (play->media_info_button)) { const gchar *title; title = gst_player_media_info_get_title (media_info); if (title) set_title (play, title); - gtk_widget_set_sensitive (play->media_info, TRUE); + gtk_widget_set_sensitive (play->media_info_button, TRUE); } } From 1232e80e202d6bd2d9c9d84c6c47e65249bbf0e3 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 6 May 2015 11:56:14 -0500 Subject: [PATCH 070/412] playback/player: gtk-play: add track selection menu --- playback/player/gtk/gtk-play.c | 273 +++++++++++++++++++++++++++++++-- 1 file changed, 257 insertions(+), 16 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8f6a77cf25..7706b920a5 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -186,6 +186,18 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); } +static const gchar * +audio_channels_string (gint num) +{ + if (num == 1) + return "mono"; + else if (num == 2) + return "stereo"; + else if (num > 2) + return "surround"; + else + return "unknown"; +} static gchar * stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { @@ -202,6 +214,8 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { gchar *buffer; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; + if (!gst_player_audio_info_get_language (audio)) + return NULL; buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", gst_player_audio_info_get_language (audio)); return buffer; @@ -210,8 +224,8 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { gchar *buffer; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; - buffer = g_strdup_printf ("%s%d", label ? "Channels : " : "", - gst_player_audio_info_get_channels (audio)); + buffer = g_strdup_printf ("%s%s", label ? "Channels : " : "", + audio_channels_string (gst_player_audio_info_get_channels (audio))); return buffer; } case SUBTITLE_INFO_CODEC: @@ -502,6 +516,240 @@ volume_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) gst_player_set_volume (play->player, value); } +static gint +_get_current_track_index (GtkPlay * play, void * (*func) (GstPlayer * player)) +{ + void *obj; + gint index = -1; + + obj = func (play->player); + if (obj) { + index = gst_player_stream_info_get_index ((GstPlayerStreamInfo*) obj); + g_object_unref (obj); + } + + return index; +} + +static gint +get_current_track_index (GtkPlay * play, GType type) +{ + if (type == GST_TYPE_PLAYER_VIDEO_INFO) + return _get_current_track_index (play, + (void*) gst_player_get_current_video_track); + else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + return _get_current_track_index (play, + (void*) gst_player_get_current_audio_track); + else + return _get_current_track_index (play, + (void*) gst_player_get_current_subtitle_track); +} + +static gchar * +get_menu_label (GstPlayerStreamInfo *stream, GType type) +{ + if (type == GST_TYPE_PLAYER_AUDIO_INFO) { + gchar *label = NULL; + gchar *lang, *codec, *channels; + + /* label format: [language] */ + lang = stream_info_get_string (stream, AUDIO_INFO_LANGUAGE, FALSE); + codec = stream_info_get_string (stream, AUDIO_INFO_CODEC, FALSE); + channels = stream_info_get_string (stream, AUDIO_INFO_CHANNELS, FALSE); + + if (lang) { + label = g_strdup_printf ("%s %s [%s]", codec ? codec : "", + channels ? channels : "", lang); + g_free (lang); + } + else + label = g_strdup_printf ("%s %s", codec ? codec : "", + channels ? channels : ""); + + g_free (codec); + g_free (channels); + return label; + } else if (type == GST_TYPE_PLAYER_VIDEO_INFO) { + /* label format: */ + return stream_info_get_string (stream, VIDEO_INFO_CODEC, FALSE); + } else { + /* label format: */ + return stream_info_get_string (stream, SUBTITLE_INFO_LANGUAGE, FALSE); + } + + return NULL; +} + +static void +disable_track (GtkPlay * play, GType type) +{ + if (type == GST_TYPE_PLAYER_VIDEO_INFO) + gst_player_set_video_track_enabled (play->player, FALSE); + else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + gst_player_set_audio_track_enabled (play->player, FALSE); + else + gst_player_set_subtitle_track_enabled (play->player, FALSE); +} + +static void +change_track (GtkPlay * play, gint index, GType type) +{ + if (type == GST_TYPE_PLAYER_VIDEO_INFO) { + gst_player_set_video_track (play->player, index); + gst_player_set_video_track_enabled (play->player, TRUE); + } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) { + gst_player_set_audio_track (play->player, index); + gst_player_set_audio_track_enabled (play->player, TRUE); + } else { + gst_player_set_subtitle_track (play->player, index); + gst_player_set_subtitle_track_enabled (play->player, TRUE); + } +} + +static void +track_changed_cb (GtkWidget * widget, GtkPlay * play) +{ + GType type; + gint index; + + /* check if button is toggled */ + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(widget))) + return; + + index = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(widget), "index")); + type = GPOINTER_TO_SIZE (g_object_get_data(G_OBJECT(widget), "type")); + + if (index == -1) + disable_track (play, type); + else + change_track (play, index, type); +} + +static GtkWidget * +create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, + GType type) +{ + GtkWidget *menu; + GtkWidget *item; + GList *list, *l; + gint current_index; + GSList *group = NULL; + + current_index = get_current_track_index (play, type); + + if (type == GST_TYPE_PLAYER_VIDEO_INFO) + list = gst_player_get_video_streams (media_info); + else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + list = gst_player_get_audio_streams (media_info); + else + list = gst_player_get_subtitle_streams (media_info); + + menu = gtk_menu_new (); + + for (l = list; l != NULL; l = l->next) { + gint index; + gchar *buffer; + GstPlayerStreamInfo *s = (GstPlayerStreamInfo*) l->data; + + buffer = get_menu_label (s, type); + item = gtk_radio_menu_item_new_with_label (group, buffer); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM(item)); + index = gst_player_stream_info_get_index (s); + g_object_set_data (G_OBJECT(item), "index", GINT_TO_POINTER (index)); + g_object_set_data (G_OBJECT(item), "type", GSIZE_TO_POINTER (type)); + if (current_index == index) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + g_free (buffer); + g_signal_connect (G_OBJECT(item), "toggled", + G_CALLBACK (track_changed_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), item); + } + item = gtk_radio_menu_item_new_with_label (group, "Disable"); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM(item)); + g_object_set_data (G_OBJECT(item), "index", GINT_TO_POINTER (-1)); + g_object_set_data (G_OBJECT(item), "type", GSIZE_TO_POINTER (type)); + if (current_index == -1) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + g_signal_connect (G_OBJECT(item), "toggled", + G_CALLBACK (track_changed_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), item); + return menu; +} + +static void +gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) +{ + GtkWidget *menu; + GtkWidget *info; + GtkWidget *audio; + GtkWidget *video; + GtkWidget *sub; + GtkWidget *submenu; + + GstPlayerMediaInfo *media_info; + + media_info = gst_player_get_media_info (play->player); + if (!media_info) + return; + + menu = gtk_menu_new (); + info = gtk_menu_item_new_with_label ("Media Information"); + audio = gtk_menu_item_new_with_label ("Audio"); + video = gtk_menu_item_new_with_label ("Video"); + sub = gtk_menu_item_new_with_label ("Subtitle"); + + if (!gst_player_get_video_streams (media_info)) + gtk_widget_set_sensitive (video, FALSE); + else { + submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_VIDEO_INFO); + if (submenu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM(video), submenu); + } + + if (!gst_player_get_audio_streams (media_info)) + gtk_widget_set_sensitive (audio, FALSE); + else { + submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_AUDIO_INFO); + if (submenu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM(audio), submenu); + } + + if (!gst_player_get_subtitle_streams (media_info)) + gtk_widget_set_sensitive (sub, FALSE); + else { + submenu = create_tracks_menu (play, media_info, + GST_TYPE_PLAYER_SUBTITLE_INFO); + if (submenu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM(sub), submenu); + } + + g_signal_connect (G_OBJECT (info), "activate", + G_CALLBACK (media_info_clicked_cb), play); + + gtk_menu_shell_append (GTK_MENU_SHELL(menu), video); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), audio); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), sub); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), info); + + gtk_widget_show_all (menu); + gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, + (event != NULL) ? event->button : 0, + gdk_event_get_time((GdkEvent*)event)); + + g_object_unref (media_info); +} + +static void +mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, + GtkPlay *play) +{ + /* we only care about right button pressed event */ + if (event->button != 3) + return; + + gtk_player_popup_menu_create (play, event); +} + static void create_ui (GtkPlay * play) { @@ -515,6 +763,13 @@ create_ui (GtkPlay * play) play->video_area = gtk_drawing_area_new (); g_signal_connect (play->video_area, "realize", G_CALLBACK (video_area_realize_cb), play); + g_signal_connect (play->video_area, "button-press-event", + G_CALLBACK (mouse_button_pressed_cb), play); + gtk_widget_set_events (play->video_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); /* Unified play/pause button */ play->play_pause_button = @@ -581,8 +836,6 @@ create_ui (GtkPlay * play) gtk_widget_realize (play->video_area); gtk_widget_show_all (play->window); - - gtk_widget_hide (play->video_area); } static void @@ -610,16 +863,6 @@ position_updated_cb (GstPlayer * unused, GstClockTime position, GtkPlay * play) play->seekbar_value_changed_signal_id); } -static void -video_dimensions_changed_cb (GstPlayer * unused, gint width, gint height, - GtkPlay * play) -{ - if (width > 0 && height > 0) - gtk_widget_show (play->video_area); - else - gtk_widget_hide (play->video_area); -} - static void eos_cb (GstPlayer * unused, GtkPlay * play) { @@ -751,8 +994,6 @@ main (gint argc, gchar ** argv) G_CALLBACK (position_updated_cb), &play); g_signal_connect (play.player, "duration-changed", G_CALLBACK (duration_changed_cb), &play); - g_signal_connect (play.player, "video-dimensions-changed", - G_CALLBACK (video_dimensions_changed_cb), &play); g_signal_connect (play.player, "end-of-stream", G_CALLBACK (eos_cb), &play); g_signal_connect (play.player, "media-info-updated", G_CALLBACK (media_info_updated_cb), &play); From 7396c221ed2ea822bf6b24e52d34b00919d0fd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 7 May 2015 13:21:02 +0200 Subject: [PATCH 071/412] playback/player: Fix indention --- playback/player/gtk/gtk-play.c | 82 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 7706b920a5..245db87046 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -198,6 +198,7 @@ audio_channels_string (gint num) else return "unknown"; } + static gchar * stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { @@ -517,14 +518,14 @@ volume_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) } static gint -_get_current_track_index (GtkPlay * play, void * (*func) (GstPlayer * player)) +_get_current_track_index (GtkPlay * play, void *(*func) (GstPlayer * player)) { void *obj; gint index = -1; obj = func (play->player); if (obj) { - index = gst_player_stream_info_get_index ((GstPlayerStreamInfo*) obj); + index = gst_player_stream_info_get_index ((GstPlayerStreamInfo *) obj); g_object_unref (obj); } @@ -536,17 +537,17 @@ get_current_track_index (GtkPlay * play, GType type) { if (type == GST_TYPE_PLAYER_VIDEO_INFO) return _get_current_track_index (play, - (void*) gst_player_get_current_video_track); + (void *) gst_player_get_current_video_track); else if (type == GST_TYPE_PLAYER_AUDIO_INFO) return _get_current_track_index (play, - (void*) gst_player_get_current_audio_track); + (void *) gst_player_get_current_audio_track); else return _get_current_track_index (play, - (void*) gst_player_get_current_subtitle_track); + (void *) gst_player_get_current_subtitle_track); } static gchar * -get_menu_label (GstPlayerStreamInfo *stream, GType type) +get_menu_label (GstPlayerStreamInfo * stream, GType type) { if (type == GST_TYPE_PLAYER_AUDIO_INFO) { gchar *label = NULL; @@ -559,12 +560,11 @@ get_menu_label (GstPlayerStreamInfo *stream, GType type) if (lang) { label = g_strdup_printf ("%s %s [%s]", codec ? codec : "", - channels ? channels : "", lang); + channels ? channels : "", lang); g_free (lang); - } - else + } else label = g_strdup_printf ("%s %s", codec ? codec : "", - channels ? channels : ""); + channels ? channels : ""); g_free (codec); g_free (channels); @@ -613,11 +613,11 @@ track_changed_cb (GtkWidget * widget, GtkPlay * play) gint index; /* check if button is toggled */ - if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(widget))) + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) return; - index = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(widget), "index")); - type = GPOINTER_TO_SIZE (g_object_get_data(G_OBJECT(widget), "type")); + index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "index")); + type = GPOINTER_TO_SIZE (g_object_get_data (G_OBJECT (widget), "type")); if (index == -1) disable_track (play, type); @@ -626,8 +626,7 @@ track_changed_cb (GtkWidget * widget, GtkPlay * play) } static GtkWidget * -create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, - GType type) +create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) { GtkWidget *menu; GtkWidget *item; @@ -649,30 +648,30 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, for (l = list; l != NULL; l = l->next) { gint index; gchar *buffer; - GstPlayerStreamInfo *s = (GstPlayerStreamInfo*) l->data; + GstPlayerStreamInfo *s = (GstPlayerStreamInfo *) l->data; buffer = get_menu_label (s, type); item = gtk_radio_menu_item_new_with_label (group, buffer); - group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM(item)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); index = gst_player_stream_info_get_index (s); - g_object_set_data (G_OBJECT(item), "index", GINT_TO_POINTER (index)); - g_object_set_data (G_OBJECT(item), "type", GSIZE_TO_POINTER (type)); + g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (index)); + g_object_set_data (G_OBJECT (item), "type", GSIZE_TO_POINTER (type)); if (current_index == index) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); g_free (buffer); - g_signal_connect (G_OBJECT(item), "toggled", + g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (track_changed_cb), play); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); } item = gtk_radio_menu_item_new_with_label (group, "Disable"); - group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM(item)); - g_object_set_data (G_OBJECT(item), "index", GINT_TO_POINTER (-1)); - g_object_set_data (G_OBJECT(item), "type", GSIZE_TO_POINTER (type)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (-1)); + g_object_set_data (G_OBJECT (item), "type", GSIZE_TO_POINTER (type)); if (current_index == -1) - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); - g_signal_connect (G_OBJECT(item), "toggled", + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (track_changed_cb), play); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); return menu; } @@ -703,7 +702,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_VIDEO_INFO); if (submenu) - gtk_menu_item_set_submenu (GTK_MENU_ITEM(video), submenu); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (video), submenu); } if (!gst_player_get_audio_streams (media_info)) @@ -711,7 +710,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_AUDIO_INFO); if (submenu) - gtk_menu_item_set_submenu (GTK_MENU_ITEM(audio), submenu); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (audio), submenu); } if (!gst_player_get_subtitle_streams (media_info)) @@ -720,28 +719,28 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_SUBTITLE_INFO); if (submenu) - gtk_menu_item_set_submenu (GTK_MENU_ITEM(sub), submenu); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); } g_signal_connect (G_OBJECT (info), "activate", G_CALLBACK (media_info_clicked_cb), play); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), video); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), audio); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), sub); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), info); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), video); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), audio); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); gtk_widget_show_all (menu); - gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, - (event != NULL) ? event->button : 0, - gdk_event_get_time((GdkEvent*)event)); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + (event != NULL) ? event->button : 0, + gdk_event_get_time ((GdkEvent *) event)); g_object_unref (media_info); } static void mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, - GtkPlay *play) + GtkPlay * play) { /* we only care about right button pressed event */ if (event->button != 3) @@ -766,10 +765,9 @@ create_ui (GtkPlay * play) g_signal_connect (play->video_area, "button-press-event", G_CALLBACK (mouse_button_pressed_cb), play); gtk_widget_set_events (play->video_area, GDK_EXPOSURE_MASK - | GDK_LEAVE_NOTIFY_MASK - | GDK_BUTTON_PRESS_MASK - | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK); + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); /* Unified play/pause button */ play->play_pause_button = From 59d822fc2b493e8600bf89557d56ed59a80af620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 7 May 2015 13:21:52 +0200 Subject: [PATCH 072/412] playback/player: gtk-play: Fix compiler warning gtk-play.c:269:60: warning: passing 'gint *' (aka 'int *') to parameter of type 'guint *' (aka 'unsigned int *') converts between pointers to integer types with different sign [-Wpointer-sign] gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); ^~~~~~ ../lib/gst/player/gstplayer-media-info.h:92:57: note: passing argument to parameter 'par_n' here (const GstPlayerVideoInfo* info, guint *par_n, guint *par_d); ^ gtk-play.c:269:68: warning: passing 'gint *' (aka 'int *') to parameter of type 'guint *' (aka 'unsigned int *') converts between pointers to integer types with different sign [-Wpointer-sign] gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); ^~~~~~ ../lib/gst/player/gstplayer-media-info.h:92:71: note: passing argument to parameter 'par_d' here (const GstPlayerVideoInfo* info, guint *par_n, guint *par_d); ^ --- playback/player/gtk/gtk-play.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 245db87046..5b0b41bd52 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -262,12 +262,12 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) } case VIDEO_INFO_PAR: { - gint par_d, par_n; + guint par_d, par_n; gchar *buffer; GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); - buffer = g_strdup_printf ("%s%d:%d", label ? "pixel-aspect-ratio : " : + buffer = g_strdup_printf ("%s%u:%u", label ? "pixel-aspect-ratio : " : "", par_n, par_d); return buffer; } From a31f95d06dfc67dd1331c0f5d9e575701a1644c9 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 5 May 2015 16:08:18 +0800 Subject: [PATCH 073/412] playback/player: android: Use gradle & ndk-build combo to generate the Android App Also add a video List selector. --- playback/player/android/AndroidManifest.xml | 79 --------- playback/player/android/Makefile.am | 32 +++- playback/player/android/README.md | 50 ++++++ playback/player/android/app/app.iml | 97 +++++++++++ playback/player/android/app/build.gradle | 68 ++++++++ .../android/app/src/main/AndroidManifest.xml | 97 +++++++++++ .../org/freedesktop/gstreamer/Player.java | 0 .../gstreamer/play}/GStreamerSurfaceView.java | 0 .../org/freedesktop/gstreamer/play}/Play.java | 60 +++---- .../gstreamer/play/VideoSelector.java | 151 ++++++++++++++++ .../android/{ => app/src/main}/jni/Android.mk | 21 ++- .../android/app/src/main/jni/Application.mk | 2 + .../android/{ => app/src/main}/jni/player.c | 2 +- .../src/main/res/layout/activity_player.xml | 70 ++++++++ .../res/layout/activity_video_selector.xml | 15 ++ .../src/main/res/menu/menu_video_selector.xml | 17 ++ .../app/src/main/res/values-w820dp/dimens.xml | 6 + .../app/src/main/res/values/dimens.xml | 5 + .../{ => app/src/main}/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 7 + playback/player/android/build.gradle | 15 ++ playback/player/android/build.xml | 92 ---------- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + playback/player/android/gradlew | 164 ++++++++++++++++++ playback/player/android/gradlew.bat | 90 ++++++++++ playback/player/android/res/layout/main.xml | 62 ------- playback/player/android/settings.gradle | 1 + 28 files changed, 932 insertions(+), 280 deletions(-) delete mode 100644 playback/player/android/AndroidManifest.xml create mode 100644 playback/player/android/README.md create mode 100644 playback/player/android/app/app.iml create mode 100644 playback/player/android/app/build.gradle create mode 100644 playback/player/android/app/src/main/AndroidManifest.xml rename playback/player/android/{src => app/src/main/java}/org/freedesktop/gstreamer/Player.java (100%) rename playback/player/android/{src/org/freedesktop/gstreamer/player => app/src/main/java/org/freedesktop/gstreamer/play}/GStreamerSurfaceView.java (100%) rename playback/player/android/{src/org/freedesktop/gstreamer/player => app/src/main/java/org/freedesktop/gstreamer/play}/Play.java (81%) create mode 100644 playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java rename playback/player/android/{ => app/src/main}/jni/Android.mk (60%) create mode 100644 playback/player/android/app/src/main/jni/Application.mk rename playback/player/android/{ => app/src/main}/jni/player.c (99%) create mode 100644 playback/player/android/app/src/main/res/layout/activity_player.xml create mode 100644 playback/player/android/app/src/main/res/layout/activity_video_selector.xml create mode 100644 playback/player/android/app/src/main/res/menu/menu_video_selector.xml create mode 100644 playback/player/android/app/src/main/res/values-w820dp/dimens.xml create mode 100644 playback/player/android/app/src/main/res/values/dimens.xml rename playback/player/android/{ => app/src/main}/res/values/strings.xml (70%) create mode 100644 playback/player/android/app/src/main/res/values/styles.xml create mode 100644 playback/player/android/build.gradle delete mode 100644 playback/player/android/build.xml create mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 playback/player/android/gradlew create mode 100644 playback/player/android/gradlew.bat delete mode 100644 playback/player/android/res/layout/main.xml create mode 100644 playback/player/android/settings.gradle diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/AndroidManifest.xml deleted file mode 100644 index 8b156d49d0..0000000000 --- a/playback/player/android/AndroidManifest.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/Makefile.am b/playback/player/android/Makefile.am index 2664abbab9..e9e4c74615 100644 --- a/playback/player/android/Makefile.am +++ b/playback/player/android/Makefile.am @@ -1,10 +1,24 @@ EXTRA_DIST = \ - AndroidManifest.xml \ - jni/Android.mk \ - jni/player.c \ - res/layout/main.xml \ - res/values/strings.xml \ - src/org/freedesktop/gstreamer/Player.java \ - src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ - src/org/freedesktop/gstreamer/player/Play.java - + build.gradle \ + gradlew \ + gradlew.bat \ + README.md \ + settings.gradle \ + app/build.gradle \ + app/src/main/AndroidManifest.xml \ + app/src/main/java/org/freedesktop/gstreamer/Player.java \ + app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ + app/src/main/java/org/freedesktop/gstreamer/player/Play.java \ + app/src/main/java/org/freedesktop/gstreamer/player/VideoSelector.java \ + app/src/main/jni/Android.mk \ + app/src/main/jni/Application.mk \ + app/src/main/jni/player.c \ + app/src/main/res/layout/activity_player.xml \ + app/src/main/res/layout/activity_video_selector.xml \ + app/src/main/res/menu/menu_video_selector.xml \ + app/src/main/res/values/dimens.xml \ + app/src/main/res/values/strings.xml \ + app/src/main/res/values/styles.xml \ + app/src/main/res/values-w820dp/styles.xml \ + gradle/wrapper/gradle-wrapper.jar \ + gradle/wrapper/gradle-wrapper.properties \ No newline at end of file diff --git a/playback/player/android/README.md b/playback/player/android/README.md new file mode 100644 index 0000000000..cf331f7b97 --- /dev/null +++ b/playback/player/android/README.md @@ -0,0 +1,50 @@ +GST Player Android port +======================= + +Prerequisites +------------- + +1. Install Android SDK from https://developer.android.com/sdk/ & set `sdk.dir` in **local.properties** to the installation path +2. Install Android NDK from https://developer.android.com/tools/sdk/ndk/index.html & set `ndk.dir` in **local.properties** to the installation path +3. If you have a different special directory for pkg-config or other tools (e.g. on OSX when using Homebrew), then also set this path using the `ndk.extraPath` variable in **local.properties** +4. Download the GStreamer android ports http://gstreamer.freedesktop.org/data/pkg/android/ and set `gstreamer.$ABI.dir` properties in **local.properties**: + + gstreamer.arm.dir=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ + gstreamer.armv7.dir=/path/to/gstreamer-1.0-android-armv7-release-1.4.5/ + gstreamer.x86.dir=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ + +Compiling the sample +-------------------- + +Use + + ./gradlew installDebug + +to compile and install a debug version onto all connected devices. + +Please note this component is using the new Android build system based on Gradle. More information about this is available on http://tools.android.com/tech-docs/new-build-system. + +Android Studio +-------------- + +Android Studio builds will work out of the box. Simply open `build.gradle` in this folder to import the project. + +Manual NDK build +---------------- + +It is still possible to build just the NDK portion. This will speed up the process a bit as you don't need to start gradle first and compile the complete App. +First, make sure to set `NDK_PROJECT_PATH` to this projects main source path. Additionally the SDK & NDK tools are available in `$PATH`. + + export NDK_PROJECT_PATH=$PWD/app/src/main + +Second, set the following environment variables to the GStreamer installation folders: + + export GSTREAMER_ROOT_ARM=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ + export GSTREAMER_ROOT_ARMV7=/path/to/tmp/gstreamer-1.0-android-armv7-release-1.4.5/ + export GSTREAMER_ROOT_X86=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ + +If you don't want to build all architectures, please modify the file `app/src/main/jni/Application.mk` + +Finally, within the `app/src/main/` directory, invoke: + + ndk-build \ No newline at end of file diff --git a/playback/player/android/app/app.iml b/playback/player/android/app/app.iml new file mode 100644 index 0000000000..86e3b56426 --- /dev/null +++ b/playback/player/android/app/app.iml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle new file mode 100644 index 0000000000..6039b5bd7c --- /dev/null +++ b/playback/player/android/app/build.gradle @@ -0,0 +1,68 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "org.freedesktop.gstreamer.play" + minSdkVersion 9 + targetSdkVersion 22 + + ndk { + moduleName "gstplayer" + } + } + + sourceSets { + main { + // GStreamer will generate these files. + java { + srcDir 'src/main/jni/src' + } + assets { + srcDir 'src/main/jni/assets' + } + + //Tell Gradle where to put the compiled shared library + jniLibs.srcDir 'src/main/libs' + + //disable automatic ndk-build call + jni.srcDirs = []; + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } + + // Thank you http://stackoverflow.com/q/28878689/375209 + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn ndkBuild + } + + task ndkBuild(type: Exec) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def ndkDir = properties.getProperty('ndk.dir') + environment GSTREAMER_ROOT_ARM: properties.getProperty('gstreamer.arm.dir') + environment GSTREAMER_ROOT_ARMV7: properties.getProperty('gstreamer.armv7.dir') + environment GSTREAMER_ROOT_X86: properties.getProperty('gstreamer.x86.dir') + + def ndkExtraPath = properties.getProperty('ndk.extraPath') + if (! "".equalsIgnoreCase(ndkExtraPath)) { + environment PATH: "${System.getenv("PATH")}${File.pathSeparator}${ndkExtraPath}" + } + + commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' // Enable V=1 for debugging messages. + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:22.1.1' + compile "com.android.support:recyclerview-v7:22.1.1" + compile "com.android.support:support-annotations:22.1.1" +} diff --git a/playback/player/android/app/src/main/AndroidManifest.xml b/playback/player/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6de668cec0 --- /dev/null +++ b/playback/player/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java similarity index 100% rename from playback/player/android/src/org/freedesktop/gstreamer/Player.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/GStreamerSurfaceView.java similarity index 100% rename from playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/GStreamerSurfaceView.java diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java similarity index 81% rename from playback/player/android/src/org/freedesktop/gstreamer/player/Play.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java index 2874f05de5..9be9d78b2a 100644 --- a/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java +++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java @@ -20,18 +20,13 @@ package org.freedesktop.gstreamer.play; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.PowerManager; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; @@ -42,13 +37,16 @@ import android.widget.Toast; import org.freedesktop.gstreamer.Player; -public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener { +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class Play extends AppCompatActivity implements SurfaceHolder.Callback, OnSeekBarChangeListener { private PowerManager.WakeLock wake_lock; private Player player; @Override - public void onCreate(Bundle savedInstanceState) - { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { @@ -59,7 +57,7 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC return; } - setContentView(R.layout.main); + setContentView(R.layout.activity_player); player = new Player(); @@ -88,7 +86,7 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC player.setPositionUpdatedListener(new Player.PositionUpdatedListener() { public void positionUpdated(Player player, final long position) { - runOnUiThread (new Runnable() { + runOnUiThread(new Runnable() { public void run() { sb.setProgress((int) (position / 1000000)); updateTimeWidget(); @@ -99,7 +97,7 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC player.setDurationChangedListener(new Player.DurationChangedListener() { public void durationChanged(Player player, final long duration) { - runOnUiThread (new Runnable() { + runOnUiThread(new Runnable() { public void run() { sb.setMax((int) (duration / 1000000)); updateTimeWidget(); @@ -109,31 +107,35 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC }); final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video); + player.setVideoDimensionsChangedListener(new Player.VideoDimensionsChangedListener() { public void videoDimensionsChanged(Player player, final int width, final int height) { - runOnUiThread (new Runnable() { + runOnUiThread(new Runnable() { public void run() { - Log.i ("GStreamer", "Media size changed to " + width + "x" + height); - gsv.media_width = width; - gsv.media_height = height; - runOnUiThread(new Runnable() { - public void run() { - gsv.requestLayout(); - } - }); + Log.i("GStreamer", "Media size changed to " + width + "x" + height); + if (width > 0 && height > 0) { + gsv.media_width = width; + gsv.media_height = height; + runOnUiThread(new Runnable() { + public void run() { + gsv.requestLayout(); + } + }); + } else { + Log.i("GStreamer", "Ignoring media size."); + } } }); } }); - SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); - SurfaceHolder sh = sv.getHolder(); + SurfaceHolder sh = gsv.getHolder(); sh.addCallback(this); String mediaUri = null; Intent intent = getIntent(); android.net.Uri uri = intent.getData(); - Log.i ("GStreamer", "Received URI: " + uri); + Log.i("GStreamer", "Received URI: " + uri); if (uri.getScheme().equals("content")) { android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null); cursor.moveToFirst(); @@ -152,7 +154,7 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC super.onDestroy(); } - private void updateTimeWidget () { + private void updateTimeWidget() { final TextView tv = (TextView) this.findViewById(R.id.textview_time); final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); final int pos = sb.getProgress(); @@ -160,14 +162,12 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); df.setTimeZone(TimeZone.getTimeZone("UTC")); - final String message = df.format(new Date (pos)) + " / " + df.format(new Date (max)); + final String message = df.format(new Date(pos)) + " / " + df.format(new Date(max)); tv.setText(message); } - public void surfaceChanged(SurfaceHolder holder, int format, int width, - int height) { - Log.d("GStreamer", "Surface changed to format " + format + " width " - + width + " height " + height); + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + width + " height " + height); player.setSurface(holder.getSurface()); } diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java new file mode 100644 index 0000000000..38739f7d12 --- /dev/null +++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java @@ -0,0 +1,151 @@ +/* GStreamer + * + * Copyright (C) 2015 Sebastian Roth + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +package org.freedesktop.gstreamer.play; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + + +public class VideoSelector extends AppCompatActivity implements LoaderManager.LoaderCallbacks { + + VideoAdapter adapter; + boolean sortByName = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_video_selector); + + ListView videoList = (ListView) findViewById(R.id.videoList); + adapter = new VideoAdapter(this); + videoList.setAdapter(adapter); + videoList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, final int position, final long id) { + final String videoPath = adapter.getVideoPath(position); + if (!TextUtils.isEmpty(videoPath)) { + Intent intent = new Intent(VideoSelector.this, Play.class); + intent.setData(Uri.parse("file://" + videoPath)); + startActivity(intent); + } + } + }); + + getSupportLoaderManager().initLoader(1, null, this); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_video_selector, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_about) { + return true; + } + + //noinspection SimplifiableIfStatement + if (id == R.id.action_sort) { + sortByName = !sortByName; + getSupportLoaderManager().restartLoader(1, null, this); + return true; + } + + + return super.onOptionsItemSelected(item); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + if (sortByName) { + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, + "UPPER(" + MediaStore.Video.Media.DATA + ")"); + } else { + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, + "UPPER(" + MediaStore.Video.Media.DISPLAY_NAME + ")"); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + adapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + + class VideoAdapter extends SimpleCursorAdapter { + public VideoAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2, null, + new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, + new int[]{android.R.id.text1, android.R.id.text2}, 0); + } + + @Override + public long getItemId(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0 || position >= cursor.getCount()) { + return 0; + } + cursor.moveToPosition(position); + + return cursor.getLong(0); + } + + public String getVideoPath(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0) { + return ""; + } + cursor.moveToPosition(position); + + return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); + } + } +} diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/app/src/main/jni/Android.mk similarity index 60% rename from playback/player/android/jni/Android.mk rename to playback/player/android/app/src/main/jni/Android.mk index fe4d50aaa8..7afe06251e 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/app/src/main/jni/Android.mk @@ -2,18 +2,25 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +GST_PATH := $(LOCAL_PATH)/../../../../../ + LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c ../../lib/gst/player/gstplayer-media-info.c -LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../lib +LOCAL_SRC_FILES := player.c \ + $(GST_PATH)/lib/gst/player/gstplayer.c \ + $(GST_PATH)/lib/gst/player/gstplayer-media-info.c +LOCAL_C_INCLUDES := $(GST_PATH)/lib LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) -ifndef GSTREAMER_ROOT -ifndef GSTREAMER_ROOT_ANDROID -$(error GSTREAMER_ROOT_ANDROID is not defined!) -endif -GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID) +ifeq ($(TARGET_ARCH_ABI),armeabi) + GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM) +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) + GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARMV7) +else ifeq ($(TARGET_ARCH_ABI),x86) + GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86) +else + $(error Target arch ABI $(TARGET_ARCH_ABI) not supported) endif GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ diff --git a/playback/player/android/app/src/main/jni/Application.mk b/playback/player/android/app/src/main/jni/Application.mk new file mode 100644 index 0000000000..f665244472 --- /dev/null +++ b/playback/player/android/app/src/main/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := armeabi armeabi-v7a x86 +APP_PLATFORM := android-9 \ No newline at end of file diff --git a/playback/player/android/jni/player.c b/playback/player/android/app/src/main/jni/player.c similarity index 99% rename from playback/player/android/jni/player.c rename to playback/player/android/app/src/main/jni/player.c index 8670b17d61..951f2c60dc 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/app/src/main/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "gst/player/gstplayer.h" +#include "gst/player/player.h" GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category diff --git a/playback/player/android/app/src/main/res/layout/activity_player.xml b/playback/player/android/app/src/main/res/layout/activity_player.xml new file mode 100644 index 0000000000..cd4a933791 --- /dev/null +++ b/playback/player/android/app/src/main/res/layout/activity_player.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/app/src/main/res/layout/activity_video_selector.xml b/playback/player/android/app/src/main/res/layout/activity_video_selector.xml new file mode 100644 index 0000000000..229ae17845 --- /dev/null +++ b/playback/player/android/app/src/main/res/layout/activity_video_selector.xml @@ -0,0 +1,15 @@ + + + + diff --git a/playback/player/android/app/src/main/res/menu/menu_video_selector.xml b/playback/player/android/app/src/main/res/menu/menu_video_selector.xml new file mode 100644 index 0000000000..93f44374d6 --- /dev/null +++ b/playback/player/android/app/src/main/res/menu/menu_video_selector.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/playback/player/android/app/src/main/res/values-w820dp/dimens.xml b/playback/player/android/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000000..63fc816444 --- /dev/null +++ b/playback/player/android/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/playback/player/android/app/src/main/res/values/dimens.xml b/playback/player/android/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/playback/player/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/playback/player/android/res/values/strings.xml b/playback/player/android/app/src/main/res/values/strings.xml similarity index 70% rename from playback/player/android/res/values/strings.xml rename to playback/player/android/app/src/main/res/values/strings.xml index 9587e3c40c..d3af9a3e35 100644 --- a/playback/player/android/res/values/strings.xml +++ b/playback/player/android/app/src/main/res/values/strings.xml @@ -3,4 +3,7 @@ GStreamer Play Play Pause + + About + Sort diff --git a/playback/player/android/app/src/main/res/values/styles.xml b/playback/player/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..540f2fe87c --- /dev/null +++ b/playback/player/android/app/src/main/res/values/styles.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/playback/player/android/build.gradle b/playback/player/android/build.gradle new file mode 100644 index 0000000000..01de6eff51 --- /dev/null +++ b/playback/player/android/build.gradle @@ -0,0 +1,15 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.2' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/playback/player/android/build.xml b/playback/player/android/build.xml deleted file mode 100644 index 4ef18c8f98..0000000000 --- a/playback/player/android/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/gradle/wrapper/gradle-wrapper.jar b/playback/player/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/playback/player/android/gradlew.bat b/playback/player/android/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/playback/player/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/playback/player/android/res/layout/main.xml b/playback/player/android/res/layout/main.xml deleted file mode 100644 index b745d8069e..0000000000 --- a/playback/player/android/res/layout/main.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/settings.gradle b/playback/player/android/settings.gradle new file mode 100644 index 0000000000..e7b4def49c --- /dev/null +++ b/playback/player/android/settings.gradle @@ -0,0 +1 @@ +include ':app' From d33cc6c876263381eeaa7557639713435fec42c7 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Fri, 8 May 2015 14:54:45 +0800 Subject: [PATCH 074/412] playback/player: android: Fix Windows build of the app Fixes #36 --- playback/player/android/app/build.gradle | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 6039b5bd7c..18e68bd205 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -1,3 +1,5 @@ +import org.apache.tools.ant.taskdefs.condition.Os + apply plugin: 'com.android.application' android { @@ -57,7 +59,12 @@ android { environment PATH: "${System.getenv("PATH")}${File.pathSeparator}${ndkExtraPath}" } - commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' // Enable V=1 for debugging messages. + // Enable V=1 for debugging messages. + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine "${ndkDir}/ndk-build.cmd", '-C', file('src/main/jni').absolutePath //, 'V=1' + } else { + commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' + } } } From 1b1aed87c59ee069a0548401c1b197cd7a4d8e06 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 7 May 2015 07:38:05 -0500 Subject: [PATCH 075/412] playback/player: gtk-play: display cover art We maintain two drawing widgets, image and video. Cover art is drawn in image widget and video is rendered in video widget. Based on the following conditions we show either image or video widget: - if media info does not have active video stream then hide video widget and show image widget. - if media info contains active video stream then show video widget and hide the image widget. --- playback/player/gtk/gtk-play.c | 201 ++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 5b0b41bd52..4252a847f8 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -22,7 +22,9 @@ #include #include +#include #include +#include #include #if defined (GDK_WINDOWING_X11) @@ -52,9 +54,11 @@ typedef struct GtkWidget *prev_button, *next_button; GtkWidget *seekbar; GtkWidget *video_area; + GtkWidget *image_area; GtkWidget *volume_button; GtkWidget *media_info_button; gulong seekbar_value_changed_signal_id; + GdkPixbuf *image_pixbuf; gboolean playing; } GtkPlay; @@ -86,6 +90,8 @@ enum SUBTITLE_INFO_END, }; +static void display_cover_art (GtkPlay * play, GstPlayerMediaInfo * media_info); + static void set_title (GtkPlay * play, const gchar * title) { @@ -159,6 +165,9 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) prev = g_list_previous (play->current_uri); g_return_if_fail (prev != NULL); + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->next_button, TRUE); gtk_widget_set_sensitive (play->media_info_button, FALSE); gst_player_set_uri (play->player, prev->data); @@ -177,6 +186,9 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) next = g_list_next (play->current_uri); g_return_if_fail (next != NULL); + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->media_info_button, FALSE); gst_player_set_uri (play->player, next->data); @@ -583,8 +595,10 @@ get_menu_label (GstPlayerStreamInfo * stream, GType type) static void disable_track (GtkPlay * play, GType type) { - if (type == GST_TYPE_PLAYER_VIDEO_INFO) + if (type == GST_TYPE_PLAYER_VIDEO_INFO) { gst_player_set_video_track_enabled (play->player, FALSE); + display_cover_art (play, NULL); /* display cover art */ + } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) gst_player_set_audio_track_enabled (play->player, FALSE); else @@ -597,6 +611,11 @@ change_track (GtkPlay * play, gint index, GType type) if (type == GST_TYPE_PLAYER_VIDEO_INFO) { gst_player_set_video_track (play->player, index); gst_player_set_video_track_enabled (play->player, TRUE); + /* if video area widget is not visible then make it visible */ + if (!gtk_widget_is_visible (play->video_area)) { + gtk_widget_hide (play->image_area); + gtk_widget_show (play->video_area); + } } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) { gst_player_set_audio_track (play->player, index); gst_player_set_audio_track_enabled (play->player, TRUE); @@ -749,6 +768,50 @@ mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, gtk_player_popup_menu_create (play, event); } +static gboolean +image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) +{ + if (play->image_pixbuf) { + gint width, height; + gint pix_width, pix_height; + gint x = 0, y = 0; + gdouble scalex = 0.0, scaley = 0.0; + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + pix_width = gdk_pixbuf_get_width (play->image_pixbuf); + pix_height = gdk_pixbuf_get_height (play->image_pixbuf); + + /* if image is bigger than widget then scale down otherwise center it. */ + if (width <= pix_width) + scalex = (gdouble)width / (gdouble)pix_width; + else + x = (width - pix_width) / 2; + if (height <= pix_height) + scaley = (gdouble)height / (gdouble)pix_height; + else + y = (height - pix_height) / 2; + + /* fill background with black */ + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + if (scalex > 0.0 && scaley > 0.0) + cairo_scale (cr, scalex, scaley); + + gdk_cairo_set_source_pixbuf(cr, play->image_pixbuf, x, y); + cairo_paint (cr); + } else { + /* fill background with black */ + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_paint (cr); + } + + return FALSE; +} + + static void create_ui (GtkPlay * play) { @@ -769,6 +832,17 @@ create_ui (GtkPlay * play) | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); + play->image_area = gtk_drawing_area_new (); + g_signal_connect (play->image_area, "button-press-event", + G_CALLBACK (mouse_button_pressed_cb), play); + g_signal_connect (play->image_area, "draw", + G_CALLBACK (image_area_draw_cb), play); + gtk_widget_set_events (play->image_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); + /* Unified play/pause button */ play->play_pause_button = gtk_button_new_from_icon_name ("media-playback-pause", @@ -825,6 +899,7 @@ create_ui (GtkPlay * play) main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (main_hbox), play->image_area, TRUE, TRUE, 0); main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0); @@ -832,6 +907,7 @@ create_ui (GtkPlay * play) gtk_container_add (GTK_CONTAINER (play->window), main_vbox); gtk_widget_realize (play->video_area); + gtk_widget_hide (play->video_area); gtk_widget_show_all (play->window); } @@ -873,6 +949,9 @@ eos_cb (GstPlayer * unused, GtkPlay * play) if (!gtk_widget_is_sensitive (play->prev_button)) gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->media_info_button, FALSE); @@ -893,6 +972,116 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } } +static gboolean +_has_active_stream (GtkPlay * play, void * (*func) (GstPlayer * player)) +{ + void *obj; + + obj = func (play->player); + if (obj) { + g_object_unref (obj); + return TRUE; + } + + return FALSE; +} + +static GdkPixbuf * +gst_sample_to_pixbuf (GtkPlay * play, GstSample * sample) +{ + GdkPixbufLoader *loader; + GdkPixbuf *pixbuf = NULL; + GError *err = NULL; + GstMapInfo info; + GstBuffer *buffer; + const GstStructure *caps_struct; + GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED; + + buffer = gst_sample_get_buffer (sample); + caps_struct = gst_sample_get_info (sample); + + /* if sample is retrieved from preview-image tag then caps struct + * will not be defined. */ + if (caps_struct) + gst_structure_get_enum (caps_struct, "image-type", + GST_TYPE_TAG_IMAGE_TYPE, &type); + + /* FIXME: Should we check more type ?? */ + if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) && + (type != GST_TAG_IMAGE_TYPE_UNDEFINED) && + (type != GST_TAG_IMAGE_TYPE_NONE)) { + g_print ("unsupport type ... %d \n", type); + return NULL; + } + + if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { + g_print ("failed to map gst buffer \n"); + return NULL; + } + + loader = gdk_pixbuf_loader_new (); + if (gdk_pixbuf_loader_write (loader, info.data, info.size, &err) && + gdk_pixbuf_loader_close (loader, &err)) { + pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + if (pixbuf) { + g_object_ref (pixbuf); + } + else { + g_print ("failed to convert gst buffer to pixbuf %s \n", err->message); + g_error_free (err); + } + } + + g_object_unref (loader); + gst_buffer_unmap (buffer, &info); + + return pixbuf; +} + +static void +display_cover_art (GtkPlay * play, GstPlayerMediaInfo * media_info) +{ + GstSample *sample; + GstPlayerMediaInfo *temp_media_info = NULL; + + /* hide the video widget and show image widget */ + gtk_widget_hide (play->video_area); + gtk_widget_show (play->image_area); + + /* if media information is not passed then get it from player */ + if (!media_info) + temp_media_info = media_info = gst_player_get_media_info (play->player); + + sample = gst_player_media_info_get_image_sample (media_info); + if (!sample) + goto cleanup; + + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + + play->image_pixbuf = gst_sample_to_pixbuf (play, sample); + +cleanup: + gtk_widget_queue_draw (play->image_area); /* send expose event to widget */ + + if (temp_media_info) + g_object_unref (temp_media_info); +} + +static gboolean +has_active_stream (GtkPlay * play, GType type) +{ + if (type == GST_TYPE_PLAYER_VIDEO_INFO) + return _has_active_stream (play, + (void *) gst_player_get_current_video_track); + else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + return _has_active_stream (play, + (void *) gst_player_get_current_audio_track); + else + return _has_active_stream (play, + (void *) gst_player_get_current_subtitle_track); +} + static void media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, GtkPlay * play) @@ -905,6 +1094,16 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, set_title (play, title); gtk_widget_set_sensitive (play->media_info_button, TRUE); + + /* if we have active video stream then hide image widget + * and show video widget otherwise show the cover art. + */ + if (has_active_stream (play, GST_TYPE_PLAYER_VIDEO_INFO)) { + gtk_widget_hide (play->image_area); + gtk_widget_show (play->video_area); + } else { + display_cover_art (play, media_info); + } } } From 15b1167746ae8c171df5e2e98117c723df365891 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 7 May 2015 15:21:28 -0500 Subject: [PATCH 076/412] playback/player: gtk-play: do not set window title in resume button cb. Window title is set from media-info-updated signal hence updating the window title in resume button callback will override the title set from media-info-updated signal. --- playback/player/gtk/gtk-play.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 4252a847f8..7521792b16 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -141,17 +141,11 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; } else { - gchar *title; - gst_player_play (play->player); image = gtk_image_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); - - title = gst_player_get_uri (play->player); - set_title (play, title); - g_free (title); play->playing = TRUE; } } From 9a7d2f031fa000bd3f516d20d1ae60bda6159849 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Fri, 8 May 2015 07:34:33 -0500 Subject: [PATCH 077/412] playback/player: gtk-play: add playlist loop button. --- playback/player/gtk/gtk-play.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 7521792b16..53b111ba6c 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -57,9 +57,11 @@ typedef struct GtkWidget *image_area; GtkWidget *volume_button; GtkWidget *media_info_button; + GtkWidget *repeat_button; gulong seekbar_value_changed_signal_id; GdkPixbuf *image_pixbuf; gboolean playing; + gboolean loop; } GtkPlay; enum @@ -164,6 +166,7 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->next_button, TRUE); gtk_widget_set_sensitive (play->media_info_button, FALSE); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); gst_player_set_uri (play->player, prev->data); play->current_uri = prev; gst_player_play (play->player); @@ -185,6 +188,7 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->prev_button, TRUE); gtk_widget_set_sensitive (play->media_info_button, FALSE); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); gst_player_set_uri (play->player, next->data); play->current_uri = next; gst_player_play (play->player); @@ -809,6 +813,7 @@ image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) static void create_ui (GtkPlay * play) { + GtkWidget *image; GtkWidget *controls, *main_hbox, *main_vbox; play->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); @@ -847,6 +852,7 @@ create_ui (GtkPlay * play) play->seekbar = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); gtk_scale_set_draw_value (GTK_SCALE (play->seekbar), 0); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); play->seekbar_value_changed_signal_id = g_signal_connect (G_OBJECT (play->seekbar), "value-changed", G_CALLBACK (seekbar_value_changed_cb), play); @@ -867,6 +873,15 @@ create_ui (GtkPlay * play) G_CALLBACK (skip_next_clicked_cb), play); gtk_widget_set_sensitive (play->next_button, FALSE); + /* Playlist repeat button */ + play->repeat_button = gtk_toggle_button_new (); + image = gtk_image_new_from_icon_name ("media-playlist-repeat", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (play->repeat_button), image); + if (play->loop) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->repeat_button), + TRUE); + /* Volume control button */ play->volume_button = gtk_volume_button_new (); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), @@ -886,6 +901,8 @@ create_ui (GtkPlay * play) gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->repeat_button, + FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->media_info_button, @@ -939,6 +956,10 @@ eos_cb (GstPlayer * unused, GtkPlay * play) gchar *uri; next = g_list_next (play->current_uri); + if (!next && gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(play->repeat_button))) + next = g_list_first (play->uris); + if (next) { if (!gtk_widget_is_sensitive (play->prev_button)) gtk_widget_set_sensitive (play->prev_button, TRUE); @@ -948,6 +969,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) play->image_pixbuf = NULL; gtk_widget_set_sensitive (play->media_info_button, FALSE); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); gst_player_set_uri (play->player, next->data); play->current_uri = next; @@ -1110,6 +1132,7 @@ main (gint argc, gchar ** argv) GOptionEntry options[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, "Files to play"}, + {"loop", 'l', 0, G_OPTION_ARG_NONE, &play.loop, "Repeat all"}, {NULL} }; guint list_length = 0; From eb42154608738ac6fc1c999799655b64d01eaa85 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Fri, 8 May 2015 07:34:55 -0500 Subject: [PATCH 078/412] playback/player: gst-play: add playlist loop command line option --- playback/player/gst-play/gst-play.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 02a36f5824..f22e3f23d3 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -46,6 +46,8 @@ typedef struct GstPlayer *player; GstState desired_state; + gboolean repeat; + GMainLoop *loop; } GstPlay; @@ -70,6 +72,9 @@ error_cb (GstPlayer * player, GError * err, GstPlay * play) { g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); + /* if looping is enabled, then disable it else will keep looping forever */ + play->repeat = FALSE; + /* try next item in list then */ if (!play_next (play)) { g_print ("Reached end of play list.\n"); @@ -438,8 +443,14 @@ play_uri (GstPlay * play, const gchar * next_uri) static gboolean play_next (GstPlay * play) { - if ((play->cur_idx + 1) >= play->num_uris) + if ((play->cur_idx + 1) >= play->num_uris) { + if (play->repeat) { + g_print ("Looping playlist \n"); + play->cur_idx = -1; + } + else return FALSE; + } play_uri (play, play->uris[++play->cur_idx]); return TRUE; @@ -632,6 +643,7 @@ main (int argc, char **argv) gboolean print_version = FALSE; gboolean interactive = FALSE; /* FIXME: maybe enable by default? */ gboolean shuffle = FALSE; + gboolean repeat = FALSE; gdouble volume = 1.0; gchar **filenames = NULL; gchar **uris; @@ -650,6 +662,7 @@ main (int argc, char **argv) "Volume", NULL}, {"playlist", 0, 0, G_OPTION_ARG_FILENAME, &playlist_file, "Playlist file containing input media files", NULL}, + {"loop", 0, 0, G_OPTION_ARG_NONE, &repeat, "Repeat all", NULL}, {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, {NULL} }; @@ -738,6 +751,7 @@ main (int argc, char **argv) /* prepare */ play = play_new (uris, volume); + play->repeat = repeat; if (interactive) { if (gst_play_kb_set_key_handler (keyboard_cb, play)) { From f9048974938e707ebf7220382751d37bd2789699 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Fri, 8 May 2015 08:30:32 -0500 Subject: [PATCH 079/412] playback/player: gtk-play: add fullscreen button --- playback/player/gtk/gtk-play.c | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 53b111ba6c..3770da6767 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -58,10 +58,12 @@ typedef struct GtkWidget *volume_button; GtkWidget *media_info_button; GtkWidget *repeat_button; + GtkWidget *fullscreen_button; gulong seekbar_value_changed_signal_id; GdkPixbuf *image_pixbuf; gboolean playing; gboolean loop; + gboolean fullscreen; } GtkPlay; enum @@ -514,6 +516,25 @@ media_info_clicked_cb (GtkButton * button, GtkPlay * play) g_object_unref (info); } +static void +fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) +{ + GtkWidget *image; + + if (gtk_toggle_button_get_active (widget)) { + image = gtk_image_new_from_icon_name ("view-restore", + GTK_ICON_SIZE_BUTTON); + gtk_window_fullscreen (GTK_WINDOW(play->window)); + gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); + } + else { + image = gtk_image_new_from_icon_name ("view-fullscreen", + GTK_ICON_SIZE_BUTTON); + gtk_window_unfullscreen (GTK_WINDOW(play->window)); + gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); + } +} + static void seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) { @@ -896,6 +917,17 @@ create_ui (GtkPlay * play) G_CALLBACK (media_info_clicked_cb), play); gtk_widget_set_sensitive (play->media_info_button, FALSE); + /* Full screen button */ + play->fullscreen_button = gtk_toggle_button_new (); + image = gtk_image_new_from_icon_name ("view-fullscreen", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); + g_signal_connect (G_OBJECT (play->fullscreen_button), "toggled", + G_CALLBACK (fullscreen_toggle_cb), play); + if (play->fullscreen) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->fullscreen_button), + TRUE); + controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, @@ -907,6 +939,8 @@ create_ui (GtkPlay * play) gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->media_info_button, FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->fullscreen_button, + FALSE, FALSE, 2); main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); @@ -1133,6 +1167,8 @@ main (gint argc, gchar ** argv) {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, "Files to play"}, {"loop", 'l', 0, G_OPTION_ARG_NONE, &play.loop, "Repeat all"}, + {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, &play.fullscreen, + "Show the player in fullscreen"}, {NULL} }; guint list_length = 0; From 911a010532bb74a2e0c3b4638b3a0ad2a413ef45 Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 10 May 2015 14:28:15 +0200 Subject: [PATCH 080/412] network/http-launch: Encapsulate sending http responses in their own function, so they can be easily deferred (future work) --- network/http-launch/http-launch.c | 52 ++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 2521f3b00c..eba4077f67 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -33,6 +33,7 @@ typedef struct GOutputStream *ostream; GSource *isource, *tosource; GByteArray *current_message; + gchar *http_version; } Client; static const char *known_mimetypes[] = { @@ -59,6 +60,7 @@ remove_client (Client * client) G_UNLOCK (clients); g_free (client->name); + g_free (client->http_version); if (client->isource) { g_source_destroy (client->isource); @@ -98,6 +100,25 @@ write_bytes (Client * client, const gchar * data, guint len) } } +static void +send_response_200_ok (Client * client) +{ + gchar *response; + response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", client->http_version, + content_type); + write_bytes (client, response, strlen (response)); + g_free (response); +} + +static void +send_response_404_not_found (Client * client) +{ + gchar *response; + response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", client->http_version); + write_bytes (client, response, strlen (response)); + g_free (response); +} + static void client_message (Client * client, const gchar * data, guint len) { @@ -105,43 +126,37 @@ client_message (Client * client, const gchar * data, guint len) if (g_str_has_prefix (lines[0], "HEAD")) { gchar **parts = g_strsplit (lines[0], " ", -1); - gchar *response; - const gchar *http_version; + + g_free (client->http_version); if (parts[1] && parts[2] && *parts[2] != '\0') - http_version = parts[2]; + client->http_version = g_strdup (parts[2]); else - http_version = "HTTP/1.0"; + client->http_version = g_strdup ("HTTP/1.0"); if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, - content_type); + send_response_200_ok (client); } else { - response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); + send_response_404_not_found (client); } - write_bytes (client, response, strlen (response)); - g_free (response); g_strfreev (parts); } else if (g_str_has_prefix (lines[0], "GET")) { gchar **parts = g_strsplit (lines[0], " ", -1); - gchar *response; - const gchar *http_version; gboolean ok = FALSE; + g_free (client->http_version); + if (parts[1] && parts[2] && *parts[2] != '\0') - http_version = parts[2]; + client->http_version = g_strdup (parts[2]); else - http_version = "HTTP/1.0"; + client->http_version = g_strdup ("HTTP/1.0"); if (parts[1] && strcmp (parts[1], "/") == 0) { - response = g_strdup_printf ("%s 200 OK\r\n%s\r\n", http_version, - content_type); + send_response_200_ok (client); ok = TRUE; } else { - response = g_strdup_printf ("%s 404 Not Found\r\n\r\n", http_version); + send_response_404_not_found (client); } - write_bytes (client, response, strlen (response)); - g_free (response); g_strfreev (parts); if (ok) { @@ -269,6 +284,7 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_print ("New connection %s\n", client->name); + client->http_version = g_strdup (""); client->connection = g_object_ref (connection); client->socket = g_socket_connection_get_socket (connection); client->istream = From 82c788e77f286afe4916f5e86d8594b687e679ad Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 10 May 2015 17:53:03 +0200 Subject: [PATCH 081/412] network/http-launch: Defer the "200 OK" response until caps have been resolved, in order to populate the HTTP content-type header, both on a "HEAD" or a "GET" request. --- network/http-launch/http-launch.c | 49 +++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index eba4077f67..0921ce7c4c 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -34,6 +34,7 @@ typedef struct GSource *isource, *tosource; GByteArray *current_message; gchar *http_version; + gboolean waiting_200_ok; } Client; static const char *known_mimetypes[] = { @@ -49,6 +50,8 @@ static GstElement *pipeline = NULL; static GstElement *multisocketsink = NULL; static gboolean started = FALSE; static gchar *content_type; +G_LOCK_DEFINE_STATIC (caps); +static gboolean caps_resolved = FALSE; static void remove_client (Client * client) @@ -126,6 +129,7 @@ client_message (Client * client, const gchar * data, guint len) if (g_str_has_prefix (lines[0], "HEAD")) { gchar **parts = g_strsplit (lines[0], " ", -1); + gboolean ok = FALSE; g_free (client->http_version); @@ -135,11 +139,27 @@ client_message (Client * client, const gchar * data, guint len) client->http_version = g_strdup ("HTTP/1.0"); if (parts[1] && strcmp (parts[1], "/") == 0) { - send_response_200_ok (client); + G_LOCK (caps); + if (caps_resolved) + send_response_200_ok (client); + else + client->waiting_200_ok = TRUE; + G_UNLOCK (caps); + ok = TRUE; } else { send_response_404_not_found (client); } g_strfreev (parts); + + if (ok && !started) { + g_print ("Starting pipeline\n"); + if (gst_element_set_state (pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + g_print ("Failed to start pipeline\n"); + g_main_loop_quit (loop); + } + started = TRUE; + } } else if (g_str_has_prefix (lines[0], "GET")) { gchar **parts = g_strsplit (lines[0], " ", -1); gboolean ok = FALSE; @@ -152,7 +172,12 @@ client_message (Client * client, const gchar * data, guint len) client->http_version = g_strdup ("HTTP/1.0"); if (parts[1] && strcmp (parts[1], "/") == 0) { - send_response_200_ok (client); + G_LOCK (caps); + if (caps_resolved) + send_response_200_ok (client); + else + client->waiting_200_ok = TRUE; + G_UNLOCK (caps); ok = TRUE; } else { send_response_404_not_found (client); @@ -284,6 +309,7 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_print ("New connection %s\n", client->name); + client->waiting_200_ok = FALSE; client->http_version = g_strdup (""); client->connection = g_object_ref (connection); client->socket = g_socket_connection_get_socket (connection); @@ -375,6 +401,7 @@ static void on_stream_caps_changed (GObject *obj, GParamSpec *pspec, GstPad *src_pad; GstCaps *src_caps; GstStructure *gstrc; + GList *l; src_pad = (GstPad *) obj; src_caps = gst_pad_get_current_caps (src_pad); @@ -412,6 +439,24 @@ static void on_stream_caps_changed (GObject *obj, GParamSpec *pspec, } gst_caps_unref (src_caps); + + /* Send 200 OK to those clients waiting for it */ + G_LOCK (caps); + + caps_resolved = TRUE; + + G_LOCK (clients); + for (l = clients; l; l = l->next) { + Client *cl = l->data; + if (cl->waiting_200_ok) { + send_response_200_ok (cl); + cl->waiting_200_ok = FALSE; + break; + } + } + G_UNLOCK (clients); + + G_UNLOCK (caps); } int From fdf0d5814318103bf34aaf80d32184797232e987 Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 10 May 2015 18:11:16 +0200 Subject: [PATCH 082/412] network/http-launch: Reduce duplicated code, using the same code for the HEAD and GET requests. --- network/http-launch/http-launch.c | 59 ++++++++++--------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 0921ce7c4c..91cf8d0d71 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -125,42 +125,16 @@ send_response_404_not_found (Client * client) static void client_message (Client * client, const gchar * data, guint len) { + gboolean http_head_request = FALSE; + gboolean http_get_request = FALSE; gchar **lines = g_strsplit_set (data, "\r\n", -1); - if (g_str_has_prefix (lines[0], "HEAD")) { - gchar **parts = g_strsplit (lines[0], " ", -1); - gboolean ok = FALSE; + if (g_str_has_prefix (lines[0], "HEAD")) + http_head_request = TRUE; + else if (g_str_has_prefix (lines[0], "GET")) + http_get_request = TRUE; - g_free (client->http_version); - - if (parts[1] && parts[2] && *parts[2] != '\0') - client->http_version = g_strdup (parts[2]); - else - client->http_version = g_strdup ("HTTP/1.0"); - - if (parts[1] && strcmp (parts[1], "/") == 0) { - G_LOCK (caps); - if (caps_resolved) - send_response_200_ok (client); - else - client->waiting_200_ok = TRUE; - G_UNLOCK (caps); - ok = TRUE; - } else { - send_response_404_not_found (client); - } - g_strfreev (parts); - - if (ok && !started) { - g_print ("Starting pipeline\n"); - if (gst_element_set_state (pipeline, - GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { - g_print ("Failed to start pipeline\n"); - g_main_loop_quit (loop); - } - started = TRUE; - } - } else if (g_str_has_prefix (lines[0], "GET")) { + if (http_head_request || http_get_request) { gchar **parts = g_strsplit (lines[0], " ", -1); gboolean ok = FALSE; @@ -185,14 +159,17 @@ client_message (Client * client, const gchar * data, guint len) g_strfreev (parts); if (ok) { - g_source_destroy (client->isource); - g_source_unref (client->isource); - client->isource = NULL; - g_source_destroy (client->tosource); - g_source_unref (client->tosource); - client->tosource = NULL; - g_print ("Starting to stream to %s\n", client->name); - g_signal_emit_by_name (multisocketsink, "add", client->socket); + if (http_get_request) { + /* Start streaming to client socket */ + g_source_destroy (client->isource); + g_source_unref (client->isource); + client->isource = NULL; + g_source_destroy (client->tosource); + g_source_unref (client->tosource); + client->tosource = NULL; + g_print ("Starting to stream to %s\n", client->name); + g_signal_emit_by_name (multisocketsink, "add", client->socket); + } if (!started) { g_print ("Starting pipeline\n"); From 329902bcda79ef6df829bf17a4faf9c6e54ff433 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Sun, 10 May 2015 10:18:06 -0500 Subject: [PATCH 083/412] playback/player: gtk-play: hide toolbar in fullscreen mode. --- playback/player/gtk/gtk-play.c | 96 +++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 3770da6767..36d25c7e64 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -59,11 +59,14 @@ typedef struct GtkWidget *media_info_button; GtkWidget *repeat_button; GtkWidget *fullscreen_button; + GtkWidget *toolbar; + GdkCursor *default_cursor; gulong seekbar_value_changed_signal_id; GdkPixbuf *image_pixbuf; gboolean playing; gboolean loop; gboolean fullscreen; + gint toolbar_hide_timeout; } GtkPlay; enum @@ -516,6 +519,24 @@ media_info_clicked_cb (GtkButton * button, GtkPlay * play) g_object_unref (info); } +static gboolean +toolbar_hide_func (GtkPlay * play) +{ + GdkCursor *cursor; + + /* TODO: add some animation while hiding the toolbar. */ + gtk_widget_hide (play->toolbar); + + /* hide the mouse pointer */ + cursor = gdk_cursor_new_for_display ( + gtk_widget_get_display (play->window), GDK_BLANK_CURSOR); + gdk_window_set_cursor (gtk_widget_get_window (play->window), cursor); + g_object_unref (cursor); + + play->toolbar_hide_timeout = 0; + return FALSE; +} + static void fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) { @@ -526,8 +547,20 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) GTK_ICON_SIZE_BUTTON); gtk_window_fullscreen (GTK_WINDOW(play->window)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); + + /* start timer to hide toolbar */ + if (play->toolbar_hide_timeout) + g_source_remove (play->toolbar_hide_timeout); + play->toolbar_hide_timeout = g_timeout_add_seconds (5, + (GSourceFunc) toolbar_hide_func, play); } else { + /* if toolbar hide timer is running then kill it */ + if (play->toolbar_hide_timeout) { + g_source_remove (play->toolbar_hide_timeout); + play->toolbar_hide_timeout = 0; + } + image = gtk_image_new_from_icon_name ("view-fullscreen", GTK_ICON_SIZE_BUTTON); gtk_window_unfullscreen (GTK_WINDOW(play->window)); @@ -780,11 +813,19 @@ static void mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, GtkPlay * play) { - /* we only care about right button pressed event */ - if (event->button != 3) - return; - - gtk_player_popup_menu_create (play, event); + if (event->type == GDK_2BUTTON_PRESS) { + /* toggle fullscreen on double button click */ + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON (play->fullscreen_button))) + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON (play->fullscreen_button), FALSE); + else + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); + } else if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { + /* popup menu on right button click */ + gtk_player_popup_menu_create (play, event); + } } static gboolean @@ -830,6 +871,31 @@ image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) return FALSE; } +static gboolean +gtk_show_toolbar_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +{ + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON (play->fullscreen_button))) { + + GdkCursor *cursor; + + /* if timer is running then kill it */ + if (play->toolbar_hide_timeout) { + g_source_remove (play->toolbar_hide_timeout); + play->toolbar_hide_timeout = 0; + } + + /* show mouse pointer */ + gdk_window_set_cursor (gtk_widget_get_window (play->window), + play->default_cursor); + + gtk_widget_show (play->toolbar); + play->toolbar_hide_timeout = g_timeout_add_seconds (5, + (GSourceFunc) toolbar_hide_func, play); + } + + return TRUE; +} static void create_ui (GtkPlay * play) @@ -847,21 +913,32 @@ create_ui (GtkPlay * play) G_CALLBACK (video_area_realize_cb), play); g_signal_connect (play->video_area, "button-press-event", G_CALLBACK (mouse_button_pressed_cb), play); + g_signal_connect (play->video_area, "motion-notify-event", + G_CALLBACK (gtk_show_toolbar_cb), play); + g_signal_connect (play->video_area, "scroll-event", + G_CALLBACK (gtk_show_toolbar_cb), play); gtk_widget_set_events (play->video_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK - | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK + | GDK_ENTER_NOTIFY_MASK); play->image_area = gtk_drawing_area_new (); g_signal_connect (play->image_area, "button-press-event", G_CALLBACK (mouse_button_pressed_cb), play); g_signal_connect (play->image_area, "draw", G_CALLBACK (image_area_draw_cb), play); + g_signal_connect (play->image_area, "motion-notify-event", + G_CALLBACK (gtk_show_toolbar_cb), play); + g_signal_connect (play->image_area, "scroll-event", + G_CALLBACK (gtk_show_toolbar_cb), play); gtk_widget_set_events (play->image_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK); + | GDK_POINTER_MOTION_HINT_MASK + | GDK_ENTER_NOTIFY_MASK); /* Unified play/pause button */ play->play_pause_button = @@ -928,7 +1005,7 @@ create_ui (GtkPlay * play) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); - controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + play->toolbar = controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, 2); @@ -955,6 +1032,9 @@ create_ui (GtkPlay * play) gtk_widget_hide (play->video_area); gtk_widget_show_all (play->window); + + play->default_cursor = gdk_window_get_cursor + (gtk_widget_get_window (play->toolbar)); } static void From 562c06c1506cc8dee4a5fc65036f1422ea73cb25 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Sun, 10 May 2015 22:04:07 -0500 Subject: [PATCH 084/412] playback/player: gtk-play: add Open, Quit, Next and Previous menu items in popup menu. --- playback/player/gtk/gtk-play.c | 136 ++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 29 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 36d25c7e64..35ce1025a8 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -179,6 +179,61 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); } +static GList * +open_file_dialog (GtkPlay *play) +{ + int res; + GList *uris = NULL; + GtkWidget *chooser; + GtkWidget *parent; + + parent = gtk_window_new(GTK_WINDOW_TOPLEVEL); + chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); + g_object_set (chooser, "local-only", FALSE, "select-multiple", TRUE, NULL); + gtk_window_set_transient_for (GTK_WINDOW (chooser), GTK_WINDOW (parent)); + + res = gtk_dialog_run (GTK_DIALOG (chooser)); + if (res == GTK_RESPONSE_ACCEPT) { + GSList *l; + + l = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (chooser)); + while (l) { + uris = g_list_append (uris, l->data); + l = g_slist_delete_link (l, l); + } + } + + gtk_widget_destroy (chooser); + return uris; +} + +static void +open_file_clicked_cb (GtkWidget * unused, GtkPlay *play) +{ + GList * uris, *current; + + uris = open_file_dialog (play); + if (uris) { + g_list_free_full (play->uris, g_free); + play->uris = uris; + current = g_list_first (play->uris); + + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + play->image_pixbuf = NULL; + gtk_widget_set_sensitive (play->prev_button, FALSE); + gtk_widget_set_sensitive (play->media_info_button, FALSE); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); + gst_player_set_uri (play->player, current->data); + play->current_uri = current; + gst_player_play (play->player); + set_title (play, current->data); + gtk_widget_set_sensitive (play->next_button, g_list_next (current) != NULL); + } +} + static void skip_next_clicked_cb (GtkButton * button, GtkPlay * play) { @@ -705,6 +760,9 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) gint current_index; GSList *group = NULL; + if (!media_info) + return NULL; + current_index = get_current_track_index (play, type); if (type == GST_TYPE_PLAYER_VIDEO_INFO) @@ -746,6 +804,13 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) return menu; } +static void +player_quit_clicked_cb (GtkButton * button, GtkPlay * play) +{ + gst_player_stop (play->player); + gtk_main_quit (); +} + static void gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) { @@ -754,59 +819,90 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *audio; GtkWidget *video; GtkWidget *sub; + GtkWidget *quit; + GtkWidget *next; + GtkWidget *prev; + GtkWidget *open; + GtkWidget *image; GtkWidget *submenu; - GstPlayerMediaInfo *media_info; - media_info = gst_player_get_media_info (play->player); - if (!media_info) - return; - menu = gtk_menu_new (); info = gtk_menu_item_new_with_label ("Media Information"); audio = gtk_menu_item_new_with_label ("Audio"); video = gtk_menu_item_new_with_label ("Video"); sub = gtk_menu_item_new_with_label ("Subtitle"); + open = gtk_menu_item_new_with_label ("Open"); + next = gtk_menu_item_new_with_label ("Next"); + prev = gtk_menu_item_new_with_label ("Prev"); + quit = gtk_menu_item_new_with_label ("Quit"); - if (!gst_player_get_video_streams (media_info)) + media_info = gst_player_get_media_info (play->player); + + if (media_info && !gst_player_get_video_streams (media_info)) gtk_widget_set_sensitive (video, FALSE); else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_VIDEO_INFO); if (submenu) gtk_menu_item_set_submenu (GTK_MENU_ITEM (video), submenu); + else + gtk_widget_set_sensitive (video, FALSE); } - if (!gst_player_get_audio_streams (media_info)) + if (media_info && !gst_player_get_audio_streams (media_info)) gtk_widget_set_sensitive (audio, FALSE); else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_AUDIO_INFO); if (submenu) gtk_menu_item_set_submenu (GTK_MENU_ITEM (audio), submenu); + else + gtk_widget_set_sensitive (audio, FALSE); } - if (!gst_player_get_subtitle_streams (media_info)) + if (media_info && !gst_player_get_subtitle_streams (media_info)) gtk_widget_set_sensitive (sub, FALSE); else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_SUBTITLE_INFO); if (submenu) gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); + else + gtk_widget_set_sensitive (sub, FALSE); } + gtk_widget_set_sensitive (next, g_list_next + (play->current_uri) ? TRUE : FALSE); + gtk_widget_set_sensitive (prev, g_list_previous + (play->current_uri) ? TRUE : FALSE); + gtk_widget_set_sensitive (info, media_info ? TRUE : FALSE); + + g_signal_connect (G_OBJECT (open), "activate", + G_CALLBACK (open_file_clicked_cb), play); + g_signal_connect (G_OBJECT (next), "activate", + G_CALLBACK (skip_next_clicked_cb), play); + g_signal_connect (G_OBJECT (prev), "activate", + G_CALLBACK (skip_prev_clicked_cb), play); g_signal_connect (G_OBJECT (info), "activate", G_CALLBACK (media_info_clicked_cb), play); + g_signal_connect (G_OBJECT (quit), "activate", + G_CALLBACK (player_quit_clicked_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), open); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), next); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), prev); gtk_menu_shell_append (GTK_MENU_SHELL (menu), video); gtk_menu_shell_append (GTK_MENU_SHELL (menu), audio); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); gtk_widget_show_all (menu); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0, gdk_event_get_time ((GdkEvent *) event)); - g_object_unref (media_info); + if (media_info) + g_object_unref (media_info); } static void @@ -1272,27 +1368,9 @@ main (gint argc, gchar ** argv) // FIXME: Add support for playlists and stuff /* Parse the list of the file names we have to play. */ if (!file_names) { - GtkWidget *chooser; - int res; - - chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); - g_object_set (chooser, "local-only", FALSE, "select-multiple", TRUE, NULL); - - res = gtk_dialog_run (GTK_DIALOG (chooser)); - if (res == GTK_RESPONSE_ACCEPT) { - GSList *l; - - l = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (chooser)); - while (l) { - play.uris = g_list_append (play.uris, l->data); - l = g_slist_delete_link (l, l); - } - } else { + play.uris = open_file_dialog (&play); + if (!play.uris) return 0; - } - gtk_widget_destroy (chooser); } else { guint i; From 4e899336b301b62a4581349db106bb17785c53f6 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Mon, 11 May 2015 10:31:08 +0800 Subject: [PATCH 085/412] playback/player: README.md formatting. --- playback/player/android/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/playback/player/android/README.md b/playback/player/android/README.md index cf331f7b97..2f8318fb46 100644 --- a/playback/player/android/README.md +++ b/playback/player/android/README.md @@ -9,10 +9,16 @@ Prerequisites 3. If you have a different special directory for pkg-config or other tools (e.g. on OSX when using Homebrew), then also set this path using the `ndk.extraPath` variable in **local.properties** 4. Download the GStreamer android ports http://gstreamer.freedesktop.org/data/pkg/android/ and set `gstreamer.$ABI.dir` properties in **local.properties**: +Sample local.properties: + + sdk.dir=/path/to/android-sdk/ + ndk.dir=/path/to/android-ndk/ + ndk.extraPath=/usr/local/bin gstreamer.arm.dir=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ gstreamer.armv7.dir=/path/to/gstreamer-1.0-android-armv7-release-1.4.5/ gstreamer.x86.dir=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ + Compiling the sample -------------------- @@ -47,4 +53,4 @@ If you don't want to build all architectures, please modify the file `app/src/ma Finally, within the `app/src/main/` directory, invoke: - ndk-build \ No newline at end of file + ndk-build From 21eb4b1ac765565469a1946983da3471deca4c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 11 May 2015 09:23:27 +0200 Subject: [PATCH 086/412] playback/player: android: Unref player in native_free() This makes sure it is actually shut down and does not emit any signals anymore later, and also prevents a memory leak. --- playback/player/android/app/src/main/jni/player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/android/app/src/main/jni/player.c b/playback/player/android/app/src/main/jni/player.c index 951f2c60dc..882c230e4b 100644 --- a/playback/player/android/app/src/main/jni/player.c +++ b/playback/player/android/app/src/main/jni/player.c @@ -224,6 +224,7 @@ native_free (JNIEnv * env, jobject thiz) if (!player) return; + g_object_unref (player->player); (*env)->DeleteGlobalRef (env, player->java_player); g_free (player); SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL); From 75404ed8e3fde50d13a4a4155fb2b898c1a03a55 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Mon, 11 May 2015 08:07:48 -0500 Subject: [PATCH 087/412] playback/player: gtk-play: move duplicate code into one function --- playback/player/gtk/gtk-play.c | 83 +++++++++++----------------------- 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 35ce1025a8..2c0ad77272 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -157,26 +157,34 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) } } +static void +play_current_uri (GtkPlay * play, GList * uri) +{ + /* reset the button/widget state to default */ + if (play->image_pixbuf) + g_object_unref (play->image_pixbuf); + play->image_pixbuf = NULL; + gtk_widget_set_sensitive (play->media_info_button, FALSE); + gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); + gtk_widget_set_sensitive (play->prev_button, g_list_previous (uri) != NULL); + gtk_widget_set_sensitive (play->next_button, g_list_next (uri) != NULL); + + /* play uri */ + gst_player_set_uri (play->player, uri->data); + play->current_uri = uri; + gst_player_play (play->player); + set_title (play, uri->data); +} + static void skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) { GList *prev; - gchar *cur_uri; prev = g_list_previous (play->current_uri); g_return_if_fail (prev != NULL); - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - play->image_pixbuf = NULL; - gtk_widget_set_sensitive (play->next_button, TRUE); - gtk_widget_set_sensitive (play->media_info_button, FALSE); - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); - gst_player_set_uri (play->player, prev->data); - play->current_uri = prev; - gst_player_play (play->player); - set_title (play, prev->data); - gtk_widget_set_sensitive (play->prev_button, g_list_previous (prev) != NULL); + play_current_uri (play, prev); } static GList * @@ -216,44 +224,23 @@ open_file_clicked_cb (GtkWidget * unused, GtkPlay *play) uris = open_file_dialog (play); if (uris) { + /* free existing playlist */ g_list_free_full (play->uris, g_free); - play->uris = uris; - current = g_list_first (play->uris); - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - play->image_pixbuf = NULL; - gtk_widget_set_sensitive (play->prev_button, FALSE); - gtk_widget_set_sensitive (play->media_info_button, FALSE); - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); - gst_player_set_uri (play->player, current->data); - play->current_uri = current; - gst_player_play (play->player); - set_title (play, current->data); - gtk_widget_set_sensitive (play->next_button, g_list_next (current) != NULL); + play->uris = uris; + play_current_uri (play, g_list_first (play->uris)); } } static void skip_next_clicked_cb (GtkButton * button, GtkPlay * play) { - GList *next, *l; - gchar *cur_uri; + GList *next; next = g_list_next (play->current_uri); g_return_if_fail (next != NULL); - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - play->image_pixbuf = NULL; - gtk_widget_set_sensitive (play->prev_button, TRUE); - gtk_widget_set_sensitive (play->media_info_button, FALSE); - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); - gst_player_set_uri (play->player, next->data); - play->current_uri = next; - gst_player_play (play->player); - set_title (play, next->data); - gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); + play_current_uri (play, next); } static const gchar * @@ -1171,20 +1158,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) next = g_list_first (play->uris); if (next) { - if (!gtk_widget_is_sensitive (play->prev_button)) - gtk_widget_set_sensitive (play->prev_button, TRUE); - gtk_widget_set_sensitive (play->next_button, g_list_next (next) != NULL); - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - play->image_pixbuf = NULL; - - gtk_widget_set_sensitive (play->media_info_button, FALSE); - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); - - gst_player_set_uri (play->player, next->data); - play->current_uri = next; - gst_player_play (play->player); - set_title (play, next->data); + play_current_uri (play, next); } else { GtkWidget *image; @@ -1391,12 +1365,9 @@ main (gint argc, gchar ** argv) g_object_set (play.player, "dispatch-to-main-context", TRUE, NULL); - gst_player_set_uri (play.player, g_list_first (play.uris)->data); - create_ui (&play); - if (list_length > 1) - gtk_widget_set_sensitive (play.next_button, TRUE); + play_current_uri (&play, g_list_first (play.uris)); g_signal_connect (play.player, "position-updated", G_CALLBACK (position_updated_cb), &play); From ddd72139591616e6cc00290c07dfa01c110e723b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 13 May 2015 22:30:48 +0300 Subject: [PATCH 088/412] playback/player: ios: Cast the seek slider value from float to integer after scaling https://github.com/sdroege/gst-player/issues/33 --- playback/player/ios/GstPlay/VideoViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index 5c752f2063..dcf06b24b3 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -114,7 +114,7 @@ if (!dragging_slider) return; // If this is a local file, allow scrub seeking, this is, seek as soon as the slider is moved. if (is_local_media) - gst_player_seek (player, ((long)time_slider.value) * 1000000); + gst_player_seek (player, time_slider.value * 1000000); [self updateTimeWidget]; } From 571cdae9dadd534e11161a83bb6920e6081d5869 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 19 May 2015 10:34:49 -0500 Subject: [PATCH 089/412] playback/player: gtk-play: add external subtitle selection menu --- playback/player/gtk/gtk-play.c | 65 ++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 2c0ad77272..5667b1594c 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -158,7 +158,7 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) } static void -play_current_uri (GtkPlay * play, GList * uri) +play_current_uri (GtkPlay * play, GList * uri, const gchar *ext_suburi) { /* reset the button/widget state to default */ if (play->image_pixbuf) @@ -169,8 +169,11 @@ play_current_uri (GtkPlay * play, GList * uri) gtk_widget_set_sensitive (play->prev_button, g_list_previous (uri) != NULL); gtk_widget_set_sensitive (play->next_button, g_list_next (uri) != NULL); - /* play uri */ - gst_player_set_uri (play->player, uri->data); + /* set uri or suburi */ + if (ext_suburi) + gst_player_set_subtitle_uri (play->player, ext_suburi); + else + gst_player_set_uri (play->player, uri->data); play->current_uri = uri; gst_player_play (play->player); set_title (play, uri->data); @@ -184,11 +187,11 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) prev = g_list_previous (play->current_uri); g_return_if_fail (prev != NULL); - play_current_uri (play, prev); + play_current_uri (play, prev, NULL); } static GList * -open_file_dialog (GtkPlay *play) +open_file_dialog (GtkPlay *play, gboolean multi) { int res; GList *uris = NULL; @@ -199,7 +202,7 @@ open_file_dialog (GtkPlay *play) chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); - g_object_set (chooser, "local-only", FALSE, "select-multiple", TRUE, NULL); + g_object_set (chooser, "local-only", FALSE, "select-multiple", multi, NULL); gtk_window_set_transient_for (GTK_WINDOW (chooser), GTK_WINDOW (parent)); res = gtk_dialog_run (GTK_DIALOG (chooser)); @@ -222,13 +225,13 @@ open_file_clicked_cb (GtkWidget * unused, GtkPlay *play) { GList * uris, *current; - uris = open_file_dialog (play); + uris = open_file_dialog (play, TRUE); if (uris) { /* free existing playlist */ g_list_free_full (play->uris, g_free); play->uris = uris; - play_current_uri (play, g_list_first (play->uris)); + play_current_uri (play, g_list_first (play->uris), NULL); } } @@ -240,7 +243,7 @@ skip_next_clicked_cb (GtkButton * button, GtkPlay * play) next = g_list_next (play->current_uri); g_return_if_fail (next != NULL); - play_current_uri (play, next); + play_current_uri (play, next, NULL); } static const gchar * @@ -686,6 +689,18 @@ get_menu_label (GstPlayerStreamInfo * stream, GType type) return NULL; } +static void +new_subtitle_clicked_cb (GtkWidget * unused, GtkPlay *play) +{ + GList * uri; + + uri = open_file_dialog (play, FALSE); + if (uri) { + play_current_uri (play, play->current_uri, uri->data); + g_list_free_full (uri, g_free); + } +} + static void disable_track (GtkPlay * play, GType type) { @@ -743,6 +758,7 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) { GtkWidget *menu; GtkWidget *item; + GtkWidget *sep; GList *list, *l; gint current_index; GSList *group = NULL; @@ -761,6 +777,16 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) menu = gtk_menu_new (); + if (type == GST_TYPE_PLAYER_SUBTITLE_INFO) { + GtkWidget *ext_subtitle; + ext_subtitle = gtk_menu_item_new_with_label ("New File"); + sep = gtk_separator_menu_item_new (); + g_signal_connect (G_OBJECT (ext_subtitle), "activate", + G_CALLBACK (new_subtitle_clicked_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), ext_subtitle); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); + } + for (l = list; l != NULL; l = l->next) { gint index; gchar *buffer; @@ -779,6 +805,8 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) G_CALLBACK (track_changed_cb), play); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); } + + sep = gtk_separator_menu_item_new (); item = gtk_radio_menu_item_new_with_label (group, "Disable"); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (-1)); @@ -787,7 +815,9 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (track_changed_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + return menu; } @@ -846,15 +876,12 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_widget_set_sensitive (audio, FALSE); } - if (media_info && !gst_player_get_subtitle_streams (media_info)) - gtk_widget_set_sensitive (sub, FALSE); - else { + if (media_info) { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_SUBTITLE_INFO); - if (submenu) - gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); - else - gtk_widget_set_sensitive (sub, FALSE); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); + } else { + gtk_widget_set_sensitive (sub, FALSE); } gtk_widget_set_sensitive (next, g_list_next @@ -1158,7 +1185,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) next = g_list_first (play->uris); if (next) { - play_current_uri (play, next); + play_current_uri (play, next, NULL); } else { GtkWidget *image; @@ -1342,7 +1369,7 @@ main (gint argc, gchar ** argv) // FIXME: Add support for playlists and stuff /* Parse the list of the file names we have to play. */ if (!file_names) { - play.uris = open_file_dialog (&play); + play.uris = open_file_dialog (&play, TRUE); if (!play.uris) return 0; } else { @@ -1367,7 +1394,7 @@ main (gint argc, gchar ** argv) create_ui (&play); - play_current_uri (&play, g_list_first (play.uris)); + play_current_uri (&play, g_list_first (play.uris), NULL); g_signal_connect (play.player, "position-updated", G_CALLBACK (position_updated_cb), &play); From b69cbd6b70a2ee2c8151d3af3f01c936930d61ad Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Tue, 19 May 2015 10:38:06 -0500 Subject: [PATCH 090/412] playback/player: gtk-player: add visualization selection menu item. --- playback/player/gtk/gtk-play.c | 120 ++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 5667b1594c..b91072e0b8 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -753,6 +753,85 @@ track_changed_cb (GtkWidget * widget, GtkPlay * play) change_track (play, index, type); } +static void +visualization_changed_cb (GtkWidget * widget, GtkPlay * play) +{ + gchar *name; + + if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { + + /* video_area is window-id is shared with playbin hence + * video_area widget will be used by visualization elements to + * render the visuals. If visualization is enabled then hide + * image widget and show video widget and similiarly when visualization + * is disabled then hide video widget and show imag widget. + */ + name = g_object_get_data (G_OBJECT (widget), "name"); + if (g_strcmp0 (name, "disable") == 0) { + gst_player_set_visualization_enabled (play->player, FALSE); + gtk_widget_hide (play->video_area); + gtk_widget_show (play->image_area); + } + else { + const gchar *vis_name; + + gst_player_set_visualization (play->player, name); + /* if visualization is not enabled then enable it */ + if (!(vis_name = gst_player_get_current_visualization (play->player))) { + gst_player_set_visualization_enabled (play->player, TRUE); + } + gtk_widget_hide (play->image_area); + gtk_widget_show (play->video_area); + } + } +} + +static GtkWidget * +create_visualization_menu (GtkPlay * play) +{ + gint i; + GtkWidget *menu; + GtkWidget *item; + GtkWidget *sep; + const GList *list; + GSList *group = NULL; + const gchar *cur_vis; + gchar **vis_names; + + menu = gtk_menu_new (); + cur_vis = gst_player_get_current_visualization (play->player); + vis_names = gst_player_get_visualization_elements_name (); + + for (i = 0; vis_names[i] != NULL; i++) { + gchar *label = (gchar *) vis_names[i]; + + item = gtk_radio_menu_item_new_with_label (group, label); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + if (g_strcmp0 (label, cur_vis) == 0) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + g_object_set_data (G_OBJECT (item), "name", label); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (visualization_changed_cb), play); + } + + sep = gtk_separator_menu_item_new (); + item = gtk_radio_menu_item_new_with_label (group, "Disable"); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + g_object_set_data (G_OBJECT (item), "name", "disable"); + if (cur_vis == NULL) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (visualization_changed_cb), play); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + if (vis_names) + g_free (vis_names); + + return menu; +} + static GtkWidget * create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) { @@ -842,6 +921,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *open; GtkWidget *image; GtkWidget *submenu; + GtkWidget *vis; GstPlayerMediaInfo *media_info; menu = gtk_menu_new (); @@ -853,6 +933,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) next = gtk_menu_item_new_with_label ("Next"); prev = gtk_menu_item_new_with_label ("Prev"); quit = gtk_menu_item_new_with_label ("Quit"); + vis = gtk_menu_item_new_with_label ("Visualization"); media_info = gst_player_get_media_info (play->player); @@ -876,7 +957,17 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_widget_set_sensitive (audio, FALSE); } - if (media_info) { + /* enable visualization menu for audio stream */ + if (media_info && + gst_player_get_audio_streams (media_info) && + !gst_player_get_video_streams (media_info)) { + submenu = create_visualization_menu (play); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (vis), submenu); + } else { + gtk_widget_set_sensitive (vis, FALSE); + } + + if (media_info && gst_player_get_video_streams (media_info)) { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_SUBTITLE_INFO); gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); @@ -906,6 +997,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_menu_shell_append (GTK_MENU_SHELL (menu), prev); gtk_menu_shell_append (GTK_MENU_SHELL (menu), video); gtk_menu_shell_append (GTK_MENU_SHELL (menu), audio); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), vis); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); @@ -1315,6 +1407,7 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, { if (!gtk_widget_is_sensitive (play->media_info_button)) { const gchar *title; + const gchar *vis; title = gst_player_media_info_get_title (media_info); if (title) @@ -1331,6 +1424,16 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, } else { display_cover_art (play, media_info); } + + /* if we have audio only stream and visualization is enabled + * then show video widget. + */ + vis = gst_player_get_current_visualization (play->player); + if (!has_active_stream (play, GST_TYPE_PLAYER_VIDEO_INFO) && + has_active_stream (play, GST_TYPE_PLAYER_AUDIO_INFO) && vis) { + gtk_widget_show (play->video_area); + gtk_widget_hide (play->image_area); + } } } @@ -1340,12 +1443,15 @@ main (gint argc, gchar ** argv) GtkPlay play; gchar **file_names = NULL; GOptionContext *ctx; + gboolean vis = FALSE; GOptionEntry options[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, "Files to play"}, {"loop", 'l', 0, G_OPTION_ARG_NONE, &play.loop, "Repeat all"}, {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, &play.fullscreen, "Show the player in fullscreen"}, + {"visual", 'v', 0, G_OPTION_ARG_NONE, &vis, + "Show visualization when there is no video stream"}, {NULL} }; guint list_length = 0; @@ -1394,6 +1500,18 @@ main (gint argc, gchar ** argv) create_ui (&play); + /* if visualization is enabled then use the first element */ + if (vis) { + gchar **vis_names; + vis_names = gst_player_get_visualization_elements_name (); + + if (vis_names) { + gst_player_set_visualization (play.player, vis_names[0]); + gst_player_set_visualization_enabled (play.player, TRUE); + g_free (vis_names); + } + } + play_current_uri (&play, g_list_first (play.uris), NULL); g_signal_connect (play.player, "position-updated", From 999365f0d206fe2c23a8e873b74f14efe419f726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 30 May 2015 10:48:34 +0200 Subject: [PATCH 091/412] playback/player: Fix indention --- playback/player/gtk/gtk-play.c | 83 ++++++++++++++++------------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index b91072e0b8..ddfd3c175b 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -158,7 +158,7 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) } static void -play_current_uri (GtkPlay * play, GList * uri, const gchar *ext_suburi) +play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) { /* reset the button/widget state to default */ if (play->image_pixbuf) @@ -191,14 +191,14 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) } static GList * -open_file_dialog (GtkPlay *play, gboolean multi) +open_file_dialog (GtkPlay * play, gboolean multi) { int res; GList *uris = NULL; GtkWidget *chooser; GtkWidget *parent; - parent = gtk_window_new(GTK_WINDOW_TOPLEVEL); + parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); @@ -221,9 +221,9 @@ open_file_dialog (GtkPlay *play, gboolean multi) } static void -open_file_clicked_cb (GtkWidget * unused, GtkPlay *play) +open_file_clicked_cb (GtkWidget * unused, GtkPlay * play) { - GList * uris, *current; + GList *uris, *current; uris = open_file_dialog (play, TRUE); if (uris) { @@ -573,8 +573,9 @@ toolbar_hide_func (GtkPlay * play) gtk_widget_hide (play->toolbar); /* hide the mouse pointer */ - cursor = gdk_cursor_new_for_display ( - gtk_widget_get_display (play->window), GDK_BLANK_CURSOR); + cursor = + gdk_cursor_new_for_display (gtk_widget_get_display (play->window), + GDK_BLANK_CURSOR); gdk_window_set_cursor (gtk_widget_get_window (play->window), cursor); g_object_unref (cursor); @@ -588,9 +589,8 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) GtkWidget *image; if (gtk_toggle_button_get_active (widget)) { - image = gtk_image_new_from_icon_name ("view-restore", - GTK_ICON_SIZE_BUTTON); - gtk_window_fullscreen (GTK_WINDOW(play->window)); + image = gtk_image_new_from_icon_name ("view-restore", GTK_ICON_SIZE_BUTTON); + gtk_window_fullscreen (GTK_WINDOW (play->window)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); /* start timer to hide toolbar */ @@ -598,8 +598,7 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) g_source_remove (play->toolbar_hide_timeout); play->toolbar_hide_timeout = g_timeout_add_seconds (5, (GSourceFunc) toolbar_hide_func, play); - } - else { + } else { /* if toolbar hide timer is running then kill it */ if (play->toolbar_hide_timeout) { g_source_remove (play->toolbar_hide_timeout); @@ -608,7 +607,7 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) image = gtk_image_new_from_icon_name ("view-fullscreen", GTK_ICON_SIZE_BUTTON); - gtk_window_unfullscreen (GTK_WINDOW(play->window)); + gtk_window_unfullscreen (GTK_WINDOW (play->window)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); } } @@ -690,9 +689,9 @@ get_menu_label (GstPlayerStreamInfo * stream, GType type) } static void -new_subtitle_clicked_cb (GtkWidget * unused, GtkPlay *play) +new_subtitle_clicked_cb (GtkWidget * unused, GtkPlay * play) { - GList * uri; + GList *uri; uri = open_file_dialog (play, FALSE); if (uri) { @@ -706,9 +705,8 @@ disable_track (GtkPlay * play, GType type) { if (type == GST_TYPE_PLAYER_VIDEO_INFO) { gst_player_set_video_track_enabled (play->player, FALSE); - display_cover_art (play, NULL); /* display cover art */ - } - else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + display_cover_art (play, NULL); /* display cover art */ + } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) gst_player_set_audio_track_enabled (play->player, FALSE); else gst_player_set_subtitle_track_enabled (play->player, FALSE); @@ -771,8 +769,7 @@ visualization_changed_cb (GtkWidget * widget, GtkPlay * play) gst_player_set_visualization_enabled (play->player, FALSE); gtk_widget_hide (play->video_area); gtk_widget_show (play->image_area); - } - else { + } else { const gchar *vis_name; gst_player_set_visualization (play->player, name); @@ -964,7 +961,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) submenu = create_visualization_menu (play); gtk_menu_item_set_submenu (GTK_MENU_ITEM (vis), submenu); } else { - gtk_widget_set_sensitive (vis, FALSE); + gtk_widget_set_sensitive (vis, FALSE); } if (media_info && gst_player_get_video_streams (media_info)) { @@ -1020,10 +1017,10 @@ mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (play->fullscreen_button))) gtk_toggle_button_set_active - (GTK_TOGGLE_BUTTON (play->fullscreen_button), FALSE); + (GTK_TOGGLE_BUTTON (play->fullscreen_button), FALSE); else gtk_toggle_button_set_active - (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); + (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); } else if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { /* popup menu on right button click */ gtk_player_popup_menu_create (play, event); @@ -1046,11 +1043,11 @@ image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) /* if image is bigger than widget then scale down otherwise center it. */ if (width <= pix_width) - scalex = (gdouble)width / (gdouble)pix_width; + scalex = (gdouble) width / (gdouble) pix_width; else x = (width - pix_width) / 2; if (height <= pix_height) - scaley = (gdouble)height / (gdouble)pix_height; + scaley = (gdouble) height / (gdouble) pix_height; else y = (height - pix_height) / 2; @@ -1060,9 +1057,9 @@ image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) cairo_fill (cr); if (scalex > 0.0 && scaley > 0.0) - cairo_scale (cr, scalex, scaley); + cairo_scale (cr, scalex, scaley); - gdk_cairo_set_source_pixbuf(cr, play->image_pixbuf, x, y); + gdk_cairo_set_source_pixbuf (cr, play->image_pixbuf, x, y); cairo_paint (cr); } else { /* fill background with black */ @@ -1123,8 +1120,7 @@ create_ui (GtkPlay * play) | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK - | GDK_ENTER_NOTIFY_MASK); + | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK); play->image_area = gtk_drawing_area_new (); g_signal_connect (play->image_area, "button-press-event", @@ -1136,11 +1132,10 @@ create_ui (GtkPlay * play) g_signal_connect (play->image_area, "scroll-event", G_CALLBACK (gtk_show_toolbar_cb), play); gtk_widget_set_events (play->image_area, GDK_EXPOSURE_MASK - | GDK_LEAVE_NOTIFY_MASK - | GDK_BUTTON_PRESS_MASK - | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK - | GDK_ENTER_NOTIFY_MASK); + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK); /* Unified play/pause button */ play->play_pause_button = @@ -1176,7 +1171,7 @@ create_ui (GtkPlay * play) /* Playlist repeat button */ play->repeat_button = gtk_toggle_button_new (); image = gtk_image_new_from_icon_name ("media-playlist-repeat", - GTK_ICON_SIZE_BUTTON); + GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (play->repeat_button), image); if (play->loop) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->repeat_button), @@ -1212,8 +1207,7 @@ create_ui (GtkPlay * play) gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->repeat_button, - FALSE, FALSE, 2); + gtk_box_pack_start (GTK_BOX (controls), play->repeat_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (controls), play->media_info_button, @@ -1236,7 +1230,7 @@ create_ui (GtkPlay * play) gtk_widget_show_all (play->window); play->default_cursor = gdk_window_get_cursor - (gtk_widget_get_window (play->toolbar)); + (gtk_widget_get_window (play->toolbar)); } static void @@ -1273,7 +1267,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) next = g_list_next (play->current_uri); if (!next && gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(play->repeat_button))) + (GTK_TOGGLE_BUTTON (play->repeat_button))) next = g_list_first (play->uris); if (next) { @@ -1292,7 +1286,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } static gboolean -_has_active_stream (GtkPlay * play, void * (*func) (GstPlayer * player)) +_has_active_stream (GtkPlay * play, void *(*func) (GstPlayer * player)) { void *obj; @@ -1344,8 +1338,7 @@ gst_sample_to_pixbuf (GtkPlay * play, GstSample * sample) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (pixbuf) { g_object_ref (pixbuf); - } - else { + } else { g_print ("failed to convert gst buffer to pixbuf %s \n", err->message); g_error_free (err); } @@ -1381,7 +1374,7 @@ display_cover_art (GtkPlay * play, GstPlayerMediaInfo * media_info) play->image_pixbuf = gst_sample_to_pixbuf (play, sample); cleanup: - gtk_widget_queue_draw (play->image_area); /* send expose event to widget */ + gtk_widget_queue_draw (play->image_area); /* send expose event to widget */ if (temp_media_info) g_object_unref (temp_media_info); @@ -1449,9 +1442,9 @@ main (gint argc, gchar ** argv) "Files to play"}, {"loop", 'l', 0, G_OPTION_ARG_NONE, &play.loop, "Repeat all"}, {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, &play.fullscreen, - "Show the player in fullscreen"}, + "Show the player in fullscreen"}, {"visual", 'v', 0, G_OPTION_ARG_NONE, &vis, - "Show visualization when there is no video stream"}, + "Show visualization when there is no video stream"}, {NULL} }; guint list_length = 0; From 5aa6c468a02bd7f61a87acedba43f61e183b1e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 30 May 2015 11:35:25 +0200 Subject: [PATCH 092/412] playback/player: player: Change visualization API a bit to be more user friendly and fix some leaks and other bugs --- playback/player/gtk/gtk-play.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index ddfd3c175b..fff5aec1ea 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -790,27 +790,30 @@ create_visualization_menu (GtkPlay * play) GtkWidget *menu; GtkWidget *item; GtkWidget *sep; - const GList *list; GSList *group = NULL; const gchar *cur_vis; - gchar **vis_names; + GstPlayerVisualization **viss, **p; menu = gtk_menu_new (); cur_vis = gst_player_get_current_visualization (play->player); - vis_names = gst_player_get_visualization_elements_name (); + viss = gst_player_visualizations_get (); - for (i = 0; vis_names[i] != NULL; i++) { - gchar *label = (gchar *) vis_names[i]; + p = viss; + while (*p) { + gchar *label = g_strdup ((*p)->name); item = gtk_radio_menu_item_new_with_label (group, label); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); if (g_strcmp0 (label, cur_vis) == 0) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); - g_object_set_data (G_OBJECT (item), "name", label); + g_object_set_data_full (G_OBJECT (item), "name", label, + (GDestroyNotify) g_free); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (visualization_changed_cb), play); + p++; } + gst_player_visualizations_free (viss); sep = gtk_separator_menu_item_new (); item = gtk_radio_menu_item_new_with_label (group, "Disable"); @@ -823,9 +826,6 @@ create_visualization_menu (GtkPlay * play) gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); - if (vis_names) - g_free (vis_names); - return menu; } @@ -1495,14 +1495,15 @@ main (gint argc, gchar ** argv) /* if visualization is enabled then use the first element */ if (vis) { - gchar **vis_names; - vis_names = gst_player_get_visualization_elements_name (); + GstPlayerVisualization **viss; + viss = gst_player_visualizations_get (); - if (vis_names) { - gst_player_set_visualization (play.player, vis_names[0]); + if (viss && *viss) { + gst_player_set_visualization (play.player, (*viss)->name); gst_player_set_visualization_enabled (play.player, TRUE); - g_free (vis_names); } + if (viss) + gst_player_visualizations_free (viss); } play_current_uri (&play, g_list_first (play.uris), NULL); From 76fb77bfe83471d0f3350aa63f7668231f1cf93e Mon Sep 17 00:00:00 2001 From: danny song Date: Thu, 4 Jun 2015 02:49:55 +0900 Subject: [PATCH 093/412] playback/player: gtk-play: move play_current_uri after the signal handler registration --- playback/player/gtk/gtk-play.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index fff5aec1ea..86c426b150 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1506,8 +1506,6 @@ main (gint argc, gchar ** argv) gst_player_visualizations_free (viss); } - play_current_uri (&play, g_list_first (play.uris), NULL); - g_signal_connect (play.player, "position-updated", G_CALLBACK (position_updated_cb), &play); g_signal_connect (play.player, "duration-changed", @@ -1521,6 +1519,8 @@ main (gint argc, gchar ** argv) gst_player_play (play.player); play.current_uri = g_list_first (play.uris); + play_current_uri (&play, g_list_first (play.uris), NULL); + gtk_main (); play_clear (&play); From 07a074be042d69ad6310f6c8fe4ba1e1453f2fda Mon Sep 17 00:00:00 2001 From: danny song Date: Thu, 4 Jun 2015 01:45:51 +0900 Subject: [PATCH 094/412] playback/player: gtk-play: remove duplicate code --- playback/player/gtk/gtk-play.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 86c426b150..ae288f3afe 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1514,11 +1514,6 @@ main (gint argc, gchar ** argv) g_signal_connect (play.player, "media-info-updated", G_CALLBACK (media_info_updated_cb), &play); - /* We have file(s) that need playing. */ - set_title (&play, g_list_first (play.uris)->data); - gst_player_play (play.player); - play.current_uri = g_list_first (play.uris); - play_current_uri (&play, g_list_first (play.uris), NULL); gtk_main (); From ddc42da7fe183bd4beecb98b6f6a17d00efc850a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Tue, 9 Jun 2015 17:37:50 +0200 Subject: [PATCH 095/412] playback/player: gtk-play: destroy dialog's parent window --- playback/player/gtk/gtk-play.c | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index ae288f3afe..fb90063492 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -217,6 +217,7 @@ open_file_dialog (GtkPlay * play, gboolean multi) } gtk_widget_destroy (chooser); + gtk_widget_destroy (parent); return uris; } From 91379afe55bbc18f8f7c6ef08feda02f9aec75fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Tue, 2 Jun 2015 13:03:06 +0200 Subject: [PATCH 096/412] playback/player: gtk-play: remove unused variables --- playback/player/gtk/gtk-play.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index fb90063492..1103d1fcd5 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -224,7 +224,7 @@ open_file_dialog (GtkPlay * play, gboolean multi) static void open_file_clicked_cb (GtkWidget * unused, GtkPlay * play) { - GList *uris, *current; + GList *uris; uris = open_file_dialog (play, TRUE); if (uris) { @@ -787,7 +787,6 @@ visualization_changed_cb (GtkWidget * widget, GtkPlay * play) static GtkWidget * create_visualization_menu (GtkPlay * play) { - gint i; GtkWidget *menu; GtkWidget *item; GtkWidget *sep; @@ -917,7 +916,6 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *next; GtkWidget *prev; GtkWidget *open; - GtkWidget *image; GtkWidget *submenu; GtkWidget *vis; GstPlayerMediaInfo *media_info; @@ -1077,8 +1075,6 @@ gtk_show_toolbar_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (play->fullscreen_button))) { - GdkCursor *cursor; - /* if timer is running then kill it */ if (play->toolbar_hide_timeout) { g_source_remove (play->toolbar_hide_timeout); @@ -1264,7 +1260,6 @@ eos_cb (GstPlayer * unused, GtkPlay * play) { if (play->playing) { GList *next = NULL; - gchar *uri; next = g_list_next (play->current_uri); if (!next && gtk_toggle_button_get_active @@ -1450,7 +1445,6 @@ main (gint argc, gchar ** argv) }; guint list_length = 0; GError *err = NULL; - GList *l; memset (&play, 0, sizeof (play)); From b647f6858a514225300760f98a5224880bc4cc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jun 2015 13:09:25 +0200 Subject: [PATCH 097/412] playback/player: gtk: Only go to PLAYING with the next file if we were in PLAYING state before Otherwise setting a subtitle URI in PAUSED will automatically go to PLAYING. --- playback/player/gtk/gtk-play.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 1103d1fcd5..cc7b742f7f 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -175,7 +175,10 @@ play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) else gst_player_set_uri (play->player, uri->data); play->current_uri = uri; - gst_player_play (play->player); + if (play->playing) + gst_player_play (play->player); + else + gst_player_pause (play->player); set_title (play, uri->data); } @@ -1509,6 +1512,7 @@ main (gint argc, gchar ** argv) g_signal_connect (play.player, "media-info-updated", G_CALLBACK (media_info_updated_cb), &play); + play.playing = TRUE; play_current_uri (&play, g_list_first (play.uris), NULL); gtk_main (); From 80eefca0d123cb610f9595bdf1cfd5051fbe9386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Tue, 9 Jun 2015 17:38:15 +0200 Subject: [PATCH 098/412] playback/player: gtk-play: color balance dialog --- playback/player/gtk/gtk-play.c | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index cc7b742f7f..bd6b7f5921 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -193,6 +193,100 @@ skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) play_current_uri (play, prev, NULL); } +static gboolean +color_balance_channel_change_value_cb (GtkRange * range, GtkScrollType scroll, + gdouble value, GtkPlay * play) +{ + GstPlayerColorBalanceType type; + + type = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (range), "type")); + + value = CLAMP (value, 0.0, 1.0); + gst_player_set_color_balance (play->player, type, value); + + return FALSE; +} + +static gboolean +color_balance_channel_button_press_cb (GtkWidget * widget, + GdkEventButton * event, GtkPlay * play) +{ + GstPlayerColorBalanceType type; + + if (event->type != GDK_2BUTTON_PRESS) + return FALSE; + + type = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "type")); + gtk_range_set_value (GTK_RANGE (widget), 0.5); + gst_player_set_color_balance (play->player, type, 0.5); + + return FALSE; +} + +static void +color_balance_dialog (GtkPlay * play) +{ + GtkWidget *parent; + GtkWidget *dialog; + GtkWidget *content; + GtkWidget *box; + GtkWidget *ctlbox; + GtkWidget *label; + GtkWidget *scale; + gdouble value; + guint i; + + parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); + dialog = gtk_dialog_new_with_buttons ("Color Balance", GTK_WINDOW (parent), + GTK_DIALOG_MODAL, "_Close", GTK_RESPONSE_CLOSE, NULL); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent)); + + content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_set_homogeneous (GTK_BOX (box), TRUE); + gtk_box_pack_start (GTK_BOX (content), box, TRUE, TRUE, 5); + + for (i = GST_PLAYER_COLOR_BALANCE_BRIGHTNESS; + i <= GST_PLAYER_COLOR_BALANCE_HUE; i++) { + ctlbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + label = gtk_label_new (gst_player_color_balance_type_get_name (i)); + scale = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, 0, 1, 0.5); + gtk_widget_set_size_request (scale, 0, 200); + gtk_box_pack_start (GTK_BOX (ctlbox), label, FALSE, TRUE, 2); + gtk_box_pack_end (GTK_BOX (ctlbox), scale, TRUE, TRUE, 2); + + gtk_box_pack_end (GTK_BOX (box), ctlbox, TRUE, TRUE, 2); + + value = gst_player_get_color_balance (play->player, i); + gtk_range_set_value (GTK_RANGE (scale), value); + g_object_set_data (G_OBJECT (scale), "type", GUINT_TO_POINTER (i)); + + g_signal_connect (scale, "change-value", + G_CALLBACK (color_balance_channel_change_value_cb), play); + g_signal_connect (scale, "button-press-event", + G_CALLBACK (color_balance_channel_button_press_cb), play); + } + + gtk_widget_show_all (dialog); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + gtk_widget_destroy (parent); +} + +static void +color_balance_clicked_cb (GtkWidget * unused, GtkPlay * play) +{ + if (gst_player_has_color_balance (play->player)) { + color_balance_dialog (play); + return; + } + + g_warning ("No color balance channels available."); + return; +} + static GList * open_file_dialog (GtkPlay * play, gboolean multi) { @@ -921,6 +1015,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *open; GtkWidget *submenu; GtkWidget *vis; + GtkWidget *cb; GstPlayerMediaInfo *media_info; menu = gtk_menu_new (); @@ -933,6 +1028,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) prev = gtk_menu_item_new_with_label ("Prev"); quit = gtk_menu_item_new_with_label ("Quit"); vis = gtk_menu_item_new_with_label ("Visualization"); + cb = gtk_menu_item_new_with_label ("Color Balance"); media_info = gst_player_get_media_info (play->player); @@ -979,9 +1075,13 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_widget_set_sensitive (prev, g_list_previous (play->current_uri) ? TRUE : FALSE); gtk_widget_set_sensitive (info, media_info ? TRUE : FALSE); + gtk_widget_set_sensitive (cb, gst_player_has_color_balance (play->player) ? + TRUE : FALSE); g_signal_connect (G_OBJECT (open), "activate", G_CALLBACK (open_file_clicked_cb), play); + g_signal_connect (G_OBJECT (cb), "activate", + G_CALLBACK (color_balance_clicked_cb), play); g_signal_connect (G_OBJECT (next), "activate", G_CALLBACK (skip_next_clicked_cb), play); g_signal_connect (G_OBJECT (prev), "activate", @@ -999,6 +1099,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_menu_shell_append (GTK_MENU_SHELL (menu), vis); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), cb); gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); gtk_widget_show_all (menu); From 2a2c10ac2562ec8ab30c2c5d48972c05b1eb69c6 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 4 Jun 2015 06:51:14 -0500 Subject: [PATCH 099/412] playback/player: gtk-play: use volume-changed signal from player to update the volume slider. --- playback/player/gtk/gtk-play.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index bd6b7f5921..895ed99649 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -1530,6 +1531,25 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, } } +static void +player_volume_changed_cb (GstPlayer * player, GtkPlay * play) +{ + gdouble new_val, cur_val; + + cur_val = gtk_scale_button_get_value + (GTK_SCALE_BUTTON (play->volume_button)); + new_val = gst_player_get_volume (play->player); + + if (fabs (cur_val - new_val) > 0.001) { + g_signal_handlers_block_by_func (play->volume_button, + volume_changed_cb, play); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), + new_val); + g_signal_handlers_unblock_by_func (play->volume_button, + volume_changed_cb, play); + } +} + int main (gint argc, gchar ** argv) { @@ -1612,6 +1632,8 @@ main (gint argc, gchar ** argv) g_signal_connect (play.player, "end-of-stream", G_CALLBACK (eos_cb), &play); g_signal_connect (play.player, "media-info-updated", G_CALLBACK (media_info_updated_cb), &play); + g_signal_connect (play.player, "volume-changed", + G_CALLBACK (player_volume_changed_cb), &play); play.playing = TRUE; play_current_uri (&play, g_list_first (play.uris), NULL); From 78142bf4eac690859f8457819d6f69f4301b90a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jun 2015 15:11:23 +0200 Subject: [PATCH 100/412] playback/player: gtk: Use gtkglsink if available --- playback/player/gtk/Makefile.am | 4 ++-- playback/player/gtk/gtk-play.c | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am index 03068e6227..df035556eb 100644 --- a/playback/player/gtk/Makefile.am +++ b/playback/player/gtk/Makefile.am @@ -3,8 +3,8 @@ bin_PROGRAMS = gtk-play gtk_play_SOURCES = gtk-play.c LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ - $(GSTREAMER_LIBS) $(GTK_LIBS) $(GLIB_LIBS) $(LIBM) + $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) -AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GLIB_CFLAGS) +AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) noinst_HEADERS = diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 895ed99649..d39658fe8a 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1203,15 +1203,28 @@ create_ui (GtkPlay * play) { GtkWidget *image; GtkWidget *controls, *main_hbox, *main_vbox; + GstElement *playbin, *video_sink, *gl_sink; play->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (play->window), "delete-event", G_CALLBACK (delete_event_cb), play); set_title (play, APP_NAME); - play->video_area = gtk_drawing_area_new (); - g_signal_connect (play->video_area, "realize", - G_CALLBACK (video_area_realize_cb), play); + gl_sink = gst_element_factory_make ("gtkglsink", NULL); + if (gl_sink) { + g_object_get (gl_sink, "widget", &play->video_area, NULL); + + video_sink = gst_element_factory_make ("glsinkbin", NULL); + g_object_set (video_sink, "sink", gl_sink, NULL); + + playbin = gst_player_get_pipeline (play->player); + g_object_set (playbin, "video-sink", video_sink, NULL); + gst_object_unref (playbin); + } else { + play->video_area = gtk_drawing_area_new (); + g_signal_connect (play->video_area, "realize", + G_CALLBACK (video_area_realize_cb), play); + } g_signal_connect (play->video_area, "button-press-event", G_CALLBACK (mouse_button_pressed_cb), play); g_signal_connect (play->video_area, "motion-notify-event", @@ -1536,8 +1549,7 @@ player_volume_changed_cb (GstPlayer * player, GtkPlay * play) { gdouble new_val, cur_val; - cur_val = gtk_scale_button_get_value - (GTK_SCALE_BUTTON (play->volume_button)); + cur_val = gtk_scale_button_get_value (GTK_SCALE_BUTTON (play->volume_button)); new_val = gst_player_get_volume (play->player); if (fabs (cur_val - new_val) > 0.001) { @@ -1570,6 +1582,10 @@ main (gint argc, gchar ** argv) guint list_length = 0; GError *err = NULL; +#if defined (GDK_WINDOWING_X11) + XInitThreads (); +#endif + memset (&play, 0, sizeof (play)); g_set_prgname (APP_NAME); From 362b5ce2a5e5d0e981c651246dd14395023512e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jun 2015 16:25:49 +0200 Subject: [PATCH 101/412] playback/player: gtk: Remove redundant code --- playback/player/gtk/gtk-play.c | 1 - 1 file changed, 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index d39658fe8a..8ae4fc3219 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1651,7 +1651,6 @@ main (gint argc, gchar ** argv) g_signal_connect (play.player, "volume-changed", G_CALLBACK (player_volume_changed_cb), &play); - play.playing = TRUE; play_current_uri (&play, g_list_first (play.uris), NULL); gtk_main (); From b5e35ad70bd171c9fd4748d8c3fcba7f60ccb4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jun 2015 18:42:38 +0200 Subject: [PATCH 102/412] playback/player: gtk: Port to GtkApplication And also clean up some other things. https://github.com/sdroege/gst-player/issues/56 --- playback/player/gtk/gtk-play.c | 390 +++++++++++++++++++++++---------- 1 file changed, 279 insertions(+), 111 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8ae4fc3219..df0f5b9a34 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -42,15 +42,22 @@ #define APP_NAME "gtk-play" +typedef GtkApplication GtkPlayApp; +typedef GtkApplicationClass GtkPlayAppClass; + +G_DEFINE_TYPE (GtkPlayApp, gtk_play_app, GTK_TYPE_APPLICATION); + typedef struct { + GtkApplicationWindow parent; + GstPlayer *player; gchar *uri; GList *uris; GList *current_uri; - GtkWidget *window; + GtkWidget *play_pause_button; GtkWidget *prev_button, *next_button; GtkWidget *seekbar; @@ -67,9 +74,27 @@ typedef struct gboolean playing; gboolean loop; gboolean fullscreen; + gboolean visual; gint toolbar_hide_timeout; } GtkPlay; +typedef GtkApplicationWindowClass GtkPlayClass; + +G_DEFINE_TYPE (GtkPlay, gtk_play, GTK_TYPE_APPLICATION_WINDOW); + +enum +{ + PROP_0, + PROP_LOOP, + PROP_FULLSCREEN, + PROP_VISUAL, + PROP_URIS, + + LAST_PROP +}; + +static GParamSpec *gtk_play_properties[LAST_PROP] = { NULL, }; + enum { COL_TEXT = 0, @@ -104,9 +129,9 @@ static void set_title (GtkPlay * play, const gchar * title) { if (title == NULL) { - gtk_window_set_title (GTK_WINDOW (play->window), APP_NAME); + gtk_window_set_title (GTK_WINDOW (play), APP_NAME); } else { - gtk_window_set_title (GTK_WINDOW (play->window), title); + gtk_window_set_title (GTK_WINDOW (play), title); } } @@ -114,7 +139,6 @@ static void delete_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { gst_player_stop (play->player); - gtk_main_quit (); } static void @@ -227,7 +251,6 @@ color_balance_channel_button_press_cb (GtkWidget * widget, static void color_balance_dialog (GtkPlay * play) { - GtkWidget *parent; GtkWidget *dialog; GtkWidget *content; GtkWidget *box; @@ -237,10 +260,9 @@ color_balance_dialog (GtkPlay * play) gdouble value; guint i; - parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); - dialog = gtk_dialog_new_with_buttons ("Color Balance", GTK_WINDOW (parent), - GTK_DIALOG_MODAL, "_Close", GTK_RESPONSE_CLOSE, NULL); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent)); + dialog = gtk_dialog_new_with_buttons ("Color Balance", GTK_WINDOW (play), + GTK_DIALOG_DESTROY_WITH_PARENT, "_Close", GTK_RESPONSE_CLOSE, NULL); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (play)); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); @@ -273,7 +295,6 @@ color_balance_dialog (GtkPlay * play) gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); - gtk_widget_destroy (parent); } static void @@ -296,7 +317,14 @@ open_file_dialog (GtkPlay * play, gboolean multi) GtkWidget *chooser; GtkWidget *parent; - parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); + if (play) { + parent = GTK_WIDGET (play); + } else { + parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (parent)); + } + chooser = gtk_file_chooser_dialog_new ("Select files to play", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); @@ -315,7 +343,9 @@ open_file_dialog (GtkPlay * play, gboolean multi) } gtk_widget_destroy (chooser); - gtk_widget_destroy (parent); + if (!play) + gtk_widget_destroy (parent); + return uris; } @@ -603,6 +633,7 @@ create_media_info_window (GtkPlay * play, GstPlayerMediaInfo * info) gtk_window_set_default_size (GTK_WINDOW (window), 550, 450); gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); gtk_container_set_border_width (GTK_CONTAINER (window), 10); + gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (play)); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); gtk_container_add (GTK_CONTAINER (window), vbox); @@ -673,9 +704,10 @@ toolbar_hide_func (GtkPlay * play) /* hide the mouse pointer */ cursor = - gdk_cursor_new_for_display (gtk_widget_get_display (play->window), + gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (play)), GDK_BLANK_CURSOR); - gdk_window_set_cursor (gtk_widget_get_window (play->window), cursor); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (play->video_area)), + cursor); g_object_unref (cursor); play->toolbar_hide_timeout = 0; @@ -689,7 +721,7 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) if (gtk_toggle_button_get_active (widget)) { image = gtk_image_new_from_icon_name ("view-restore", GTK_ICON_SIZE_BUTTON); - gtk_window_fullscreen (GTK_WINDOW (play->window)); + gtk_window_fullscreen (GTK_WINDOW (play)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); /* start timer to hide toolbar */ @@ -706,7 +738,7 @@ fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) image = gtk_image_new_from_icon_name ("view-fullscreen", GTK_ICON_SIZE_BUTTON); - gtk_window_unfullscreen (GTK_WINDOW (play->window)); + gtk_window_unfullscreen (GTK_WINDOW (play)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); } } @@ -998,8 +1030,7 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) static void player_quit_clicked_cb (GtkButton * button, GtkPlay * play) { - gst_player_stop (play->player); - gtk_main_quit (); + gtk_widget_destroy (GTK_WIDGET (play)); } static void @@ -1187,8 +1218,8 @@ gtk_show_toolbar_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) } /* show mouse pointer */ - gdk_window_set_cursor (gtk_widget_get_window (play->window), - play->default_cursor); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET + (play->video_area)), play->default_cursor); gtk_widget_show (play->toolbar); play->toolbar_hide_timeout = g_timeout_add_seconds (5, @@ -1205,10 +1236,13 @@ create_ui (GtkPlay * play) GtkWidget *controls, *main_hbox, *main_vbox; GstElement *playbin, *video_sink, *gl_sink; - play->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - g_signal_connect (G_OBJECT (play->window), "delete-event", + gtk_window_set_default_size (GTK_WINDOW (play), 640, 480); + + g_signal_connect (G_OBJECT (play), "delete-event", G_CALLBACK (delete_event_cb), play); set_title (play, APP_NAME); + gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (play)); gl_sink = gst_element_factory_make ("gtkglsink", NULL); if (gl_sink) { @@ -1337,23 +1371,11 @@ create_ui (GtkPlay * play) main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (main_vbox), controls, FALSE, FALSE, 0); - gtk_container_add (GTK_CONTAINER (play->window), main_vbox); + gtk_container_add (GTK_CONTAINER (play), main_vbox); - gtk_widget_realize (play->video_area); + if (!gl_sink) + gtk_widget_realize (play->video_area); gtk_widget_hide (play->video_area); - - gtk_widget_show_all (play->window); - - play->default_cursor = gdk_window_get_cursor - (gtk_widget_get_window (play->toolbar)); -} - -static void -play_clear (GtkPlay * play) -{ - g_free (play->uri); - g_list_free_full (play->uris, g_free); - g_object_unref (play->player); } static void @@ -1562,100 +1584,246 @@ player_volume_changed_cb (GstPlayer * player, GtkPlay * play) } } -int -main (gint argc, gchar ** argv) +static void +gtk_play_set_property (GObject * object, guint prop_id, const GValue * value, + GParamSpec * pspec) { - GtkPlay play; - gchar **file_names = NULL; - GOptionContext *ctx; - gboolean vis = FALSE; - GOptionEntry options[] = { - {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_names, - "Files to play"}, - {"loop", 'l', 0, G_OPTION_ARG_NONE, &play.loop, "Repeat all"}, - {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, &play.fullscreen, - "Show the player in fullscreen"}, - {"visual", 'v', 0, G_OPTION_ARG_NONE, &vis, - "Show visualization when there is no video stream"}, - {NULL} - }; - guint list_length = 0; - GError *err = NULL; + GtkPlay *self = (GtkPlay *) object; -#if defined (GDK_WINDOWING_X11) - XInitThreads (); -#endif - - memset (&play, 0, sizeof (play)); - - g_set_prgname (APP_NAME); - - ctx = g_option_context_new ("FILE|URI"); - g_option_context_add_main_entries (ctx, options, NULL); - g_option_context_add_group (ctx, gtk_get_option_group (TRUE)); - g_option_context_add_group (ctx, gst_init_get_option_group ()); - if (!g_option_context_parse (ctx, &argc, &argv, &err)) { - g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); - return 1; + switch (prop_id) { + case PROP_LOOP: + self->loop = g_value_get_boolean (value); + break; + case PROP_FULLSCREEN: + self->fullscreen = g_value_get_boolean (value); + break; + case PROP_VISUAL: + self->visual = g_value_get_boolean (value); + break; + case PROP_URIS: + self->uris = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } - g_option_context_free (ctx); +} - // FIXME: Add support for playlists and stuff - /* Parse the list of the file names we have to play. */ - if (!file_names) { - play.uris = open_file_dialog (&play, TRUE); - if (!play.uris) - return 0; - } else { - guint i; +static void +show_cb (GtkWidget * widget, gpointer user_data) +{ + GtkPlay *self = (GtkPlay *) widget; - list_length = g_strv_length (file_names); - for (i = 0; i < list_length; i++) { - play.uris = - g_list_append (play.uris, - gst_uri_is_valid (file_names[i]) ? - g_strdup (file_names[i]) : gst_filename_to_uri (file_names[i], NULL)); - } + self->default_cursor = gdk_window_get_cursor + (gtk_widget_get_window (GTK_WIDGET (self))); - g_strfreev (file_names); - file_names = NULL; - } + play_current_uri (self, g_list_first (self->uris), NULL); +} - play.player = gst_player_new (); - play.playing = TRUE; +static GObject * +gtk_play_constructor (GType type, guint n_construct_params, + GObjectConstructParam * construct_params) +{ + GtkPlay *self; + gchar **p; - g_object_set (play.player, "dispatch-to-main-context", TRUE, NULL); + self = + (GtkPlay *) G_OBJECT_CLASS (gtk_play_parent_class)->constructor (type, + n_construct_params, construct_params); - create_ui (&play); + self->player = gst_player_new (); + self->playing = TRUE; + + g_object_set (self->player, "dispatch-to-main-context", TRUE, NULL); + + create_ui (self); /* if visualization is enabled then use the first element */ - if (vis) { + if (self->visual) { GstPlayerVisualization **viss; viss = gst_player_visualizations_get (); if (viss && *viss) { - gst_player_set_visualization (play.player, (*viss)->name); - gst_player_set_visualization_enabled (play.player, TRUE); + gst_player_set_visualization (self->player, (*viss)->name); + gst_player_set_visualization_enabled (self->player, TRUE); } if (viss) gst_player_visualizations_free (viss); } - g_signal_connect (play.player, "position-updated", - G_CALLBACK (position_updated_cb), &play); - g_signal_connect (play.player, "duration-changed", - G_CALLBACK (duration_changed_cb), &play); - g_signal_connect (play.player, "end-of-stream", G_CALLBACK (eos_cb), &play); - g_signal_connect (play.player, "media-info-updated", - G_CALLBACK (media_info_updated_cb), &play); - g_signal_connect (play.player, "volume-changed", - G_CALLBACK (player_volume_changed_cb), &play); + g_signal_connect (self->player, "position-updated", + G_CALLBACK (position_updated_cb), self); + g_signal_connect (self->player, "duration-changed", + G_CALLBACK (duration_changed_cb), self); + g_signal_connect (self->player, "end-of-stream", G_CALLBACK (eos_cb), self); + g_signal_connect (self->player, "media-info-updated", + G_CALLBACK (media_info_updated_cb), self); + g_signal_connect (self->player, "volume-changed", + G_CALLBACK (player_volume_changed_cb), self); - play_current_uri (&play, g_list_first (play.uris), NULL); + g_signal_connect (G_OBJECT (self), "show", G_CALLBACK (show_cb), NULL); - gtk_main (); - - play_clear (&play); - - return 0; + return G_OBJECT (self); +} + +static void +gtk_play_dispose (GObject * object) +{ + GtkPlay *self = (GtkPlay *) object; + + if (self->uri) + g_free (self->uri); + self->uri = NULL; + + if (self->uris) + g_list_free_full (self->uris, g_free); + self->uris = NULL; + if (self->player) { + gst_player_stop (self->player); + g_object_unref (self->player); + } + self->player = NULL; + + G_OBJECT_CLASS (gtk_play_parent_class)->dispose (object); +} + +static void +gtk_play_init (GtkPlay * self) +{ +} + +static void +gtk_play_class_init (GtkPlayClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gtk_play_constructor; + object_class->dispose = gtk_play_dispose; + object_class->set_property = gtk_play_set_property; + + gtk_play_properties[PROP_LOOP] = + g_param_spec_boolean ("loop", "Loop", "Loop the playlist", + FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + gtk_play_properties[PROP_FULLSCREEN] = + g_param_spec_boolean ("fullscreen", "Fullscreen", "Fullscreen mode", + FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + gtk_play_properties[PROP_VISUAL] = + g_param_spec_boolean ("visual", "Visual", "Use Visualizations", FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + gtk_play_properties[PROP_URIS] = + g_param_spec_pointer ("uris", "URIs", "URIs to play", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, + gtk_play_properties); +} + +static gint +gtk_play_app_command_line (GApplication * application, + GApplicationCommandLine * command_line) +{ + GtkPlayApp *self = (GtkPlayApp *) application; + GVariantDict *options; + GtkPlay *play; + GList *uris = NULL; + gboolean loop = FALSE, visual = FALSE, fullscreen = FALSE; + gchar **uris_array = NULL; + + options = g_application_command_line_get_options_dict (command_line); + + g_variant_dict_lookup (options, "loop", "b", &loop); + g_variant_dict_lookup (options, "visual", "b", &visual); + g_variant_dict_lookup (options, "fullscreen", "b", &fullscreen); + g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&ay", &uris_array); + + if (uris_array) { + gchar **p; + + p = uris_array; + while (*p) { + uris = + g_list_prepend (uris, + gst_uri_is_valid (*p) ? + g_strdup (*p) : gst_filename_to_uri (*p, NULL)); + p++; + } + uris = g_list_reverse (uris); + } else { + uris = open_file_dialog (NULL, TRUE); + } + + if (!uris) + return -1; + + play = + g_object_new (gtk_play_get_type (), "loop", loop, "fullscreen", + fullscreen, "visual", visual, "uris", uris, NULL); + gtk_widget_show_all (GTK_WIDGET (play)); + + return + G_APPLICATION_CLASS (gtk_play_app_parent_class)->command_line + (application, command_line); +} + +static void +gtk_play_app_init (GtkPlayApp * self) +{ +} + +static void +gtk_play_app_class_init (GtkPlayAppClass * klass) +{ + GApplicationClass *application_class = G_APPLICATION_CLASS (klass); + + application_class->command_line = gtk_play_app_command_line; +} + +GtkPlayApp * +gtk_play_app_new (void) +{ + GtkPlayApp *self; + GOptionEntry options[] = { + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, + "Files to play"}, + {"loop", 'l', 0, G_OPTION_ARG_NONE, NULL, "Repeat all"}, + {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, NULL, + "Show the player in fullscreen"}, + {"visual", 'v', 0, G_OPTION_ARG_NONE, NULL, + "Show visualization when there is no video stream"}, + {NULL} + }; + + g_set_prgname (APP_NAME); + g_set_application_name (APP_NAME); + + self = g_object_new (gtk_play_app_get_type (), + "application-id", "org.freedesktop.gstreamer.GTKPlay", + "flags", G_APPLICATION_HANDLES_COMMAND_LINE, + "register-session", TRUE, NULL); + + g_application_set_default (G_APPLICATION (self)); + g_application_add_main_option_entries (G_APPLICATION (self), options); + g_application_add_option_group (G_APPLICATION (self), + gst_init_get_option_group ()); + + return self; +} + +int +main (gint argc, gchar ** argv) +{ + GtkPlayApp *app; + gint status; + +#if defined (GDK_WINDOWING_X11) + XInitThreads (); +#endif + + app = gtk_play_app_new (); + status = g_application_run (G_APPLICATION (app), argc, argv);; + g_object_unref (app); + + return status; } From 40e21cea24b56e068b4a23ef17b5f5491c064b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jun 2015 18:52:12 +0200 Subject: [PATCH 103/412] playback/player: gtk: Inhibit the screen saver when playing something --- playback/player/gtk/gtk-play.c | 44 ++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index df0f5b9a34..8204362c09 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -57,6 +57,7 @@ typedef struct GList *uris; GList *current_uri; + guint inhibit_cookie; GtkWidget *play_pause_button; GtkWidget *prev_button, *next_button; @@ -172,7 +173,19 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; + + if (play->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + play->inhibit_cookie); + play->inhibit_cookie = 0; } else { + if (play->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + play->inhibit_cookie); + play->inhibit_cookie = + gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (play), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); + gst_player_play (play->player); image = gtk_image_new_from_icon_name ("media-playback-pause", @@ -200,10 +213,21 @@ play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) else gst_player_set_uri (play->player, uri->data); play->current_uri = uri; - if (play->playing) + if (play->playing) { + if (play->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + play->inhibit_cookie); + play->inhibit_cookie = + gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (play), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); gst_player_play (play->player); - else + } else { gst_player_pause (play->player); + if (play->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + play->inhibit_cookie); + play->inhibit_cookie = 0; + } set_title (play, uri->data); } @@ -1417,6 +1441,10 @@ eos_cb (GstPlayer * unused, GtkPlay * play) GTK_ICON_SIZE_BUTTON); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; + if (play->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default + ()), play->inhibit_cookie); + play->inhibit_cookie = 0; } } } @@ -1634,6 +1662,13 @@ gtk_play_constructor (GType type, guint n_construct_params, self->player = gst_player_new (); self->playing = TRUE; + if (self->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + self->inhibit_cookie); + self->inhibit_cookie = + gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (self), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); + g_object_set (self->player, "dispatch-to-main-context", TRUE, NULL); create_ui (self); @@ -1671,6 +1706,11 @@ gtk_play_dispose (GObject * object) { GtkPlay *self = (GtkPlay *) object; + if (self->inhibit_cookie) + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + self->inhibit_cookie); + self->inhibit_cookie = 0; + if (self->uri) g_free (self->uri); self->uri = NULL; From ba1ea118232956abdb9f494c074ccc9310e6e97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Thu, 11 Jun 2015 19:49:59 +0200 Subject: [PATCH 104/412] playback/player: gtk-play: remove unused variables --- playback/player/gtk/gtk-play.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8204362c09..66b23c5d74 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1653,7 +1653,6 @@ gtk_play_constructor (GType type, guint n_construct_params, GObjectConstructParam * construct_params) { GtkPlay *self; - gchar **p; self = (GtkPlay *) G_OBJECT_CLASS (gtk_play_parent_class)->constructor (type, @@ -1764,7 +1763,6 @@ static gint gtk_play_app_command_line (GApplication * application, GApplicationCommandLine * command_line) { - GtkPlayApp *self = (GtkPlayApp *) application; GVariantDict *options; GtkPlay *play; GList *uris = NULL; From 3afafa30c60657fc217e10c8bb7f85fd7ca092d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jun 2015 20:55:29 +0200 Subject: [PATCH 105/412] playback/player: gtk: Also use gtksink if available --- playback/player/gtk/gtk-play.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 66b23c5d74..a16f639d21 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1258,7 +1258,7 @@ create_ui (GtkPlay * play) { GtkWidget *image; GtkWidget *controls, *main_hbox, *main_vbox; - GstElement *playbin, *video_sink, *gl_sink; + GstElement *playbin, *gtk_sink; gtk_window_set_default_size (GTK_WINDOW (play), 640, 480); @@ -1268,16 +1268,23 @@ create_ui (GtkPlay * play) gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play)); - gl_sink = gst_element_factory_make ("gtkglsink", NULL); - if (gl_sink) { - g_object_get (gl_sink, "widget", &play->video_area, NULL); + if ((gtk_sink = gst_element_factory_make ("gtkglsink", NULL))) { + GstElement *video_sink; + + g_object_get (gtk_sink, "widget", &play->video_area, NULL); video_sink = gst_element_factory_make ("glsinkbin", NULL); - g_object_set (video_sink, "sink", gl_sink, NULL); + g_object_set (video_sink, "sink", gtk_sink, NULL); playbin = gst_player_get_pipeline (play->player); g_object_set (playbin, "video-sink", video_sink, NULL); gst_object_unref (playbin); + } else if ((gtk_sink = gst_element_factory_make ("gtksink", NULL))) { + g_object_get (gtk_sink, "widget", &play->video_area, NULL); + + playbin = gst_player_get_pipeline (play->player); + g_object_set (playbin, "video-sink", gtk_sink, NULL); + gst_object_unref (playbin); } else { play->video_area = gtk_drawing_area_new (); g_signal_connect (play->video_area, "realize", @@ -1397,7 +1404,7 @@ create_ui (GtkPlay * play) gtk_box_pack_start (GTK_BOX (main_vbox), controls, FALSE, FALSE, 0); gtk_container_add (GTK_CONTAINER (play), main_vbox); - if (!gl_sink) + if (!gtk_sink) gtk_widget_realize (play->video_area); gtk_widget_hide (play->video_area); } From efd3c03ab289bab43408f11357d863ba113effc7 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Mon, 15 Jun 2015 10:10:14 -0500 Subject: [PATCH 106/412] playback/player: gtk-play: add playback rate control menu item --- playback/player/gtk/gtk-play.c | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index a16f639d21..96bb25d4aa 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1051,6 +1051,46 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) return menu; } +static void +player_set_rate_cb (GtkSpinButton * button, GtkPlay * play) +{ + gst_player_set_rate (play->player, gtk_spin_button_get_value (button)); +} + +static void +player_speed_clicked_cb (GtkButton * button, GtkPlay * play) +{ + GtkWidget *box; + GtkWidget *label; + GtkWidget *dialog; + GtkWidget *content; + GtkWidget *rate_spinbutton; + + dialog = gtk_dialog_new (); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (play)); + gtk_window_set_title (GTK_WINDOW (dialog), "Playback speed control"); + + content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1); + gtk_box_pack_start (GTK_BOX (content), box, TRUE, TRUE, 0); + + label = gtk_label_new ("Playback rate"); + rate_spinbutton = gtk_spin_button_new_with_range (-64, 64, 0.1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (rate_spinbutton), 2); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (rate_spinbutton), + gst_player_get_rate (play->player)); + + gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 2); + gtk_box_pack_start (GTK_BOX (box), rate_spinbutton, TRUE, TRUE, 2); + g_signal_connect (rate_spinbutton, "value-changed", + G_CALLBACK (player_set_rate_cb), play); + + gtk_widget_show_all (dialog); + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); +} + static void player_quit_clicked_cb (GtkButton * button, GtkPlay * play) { @@ -1072,6 +1112,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *submenu; GtkWidget *vis; GtkWidget *cb; + GtkWidget *speed; GstPlayerMediaInfo *media_info; menu = gtk_menu_new (); @@ -1085,6 +1126,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) quit = gtk_menu_item_new_with_label ("Quit"); vis = gtk_menu_item_new_with_label ("Visualization"); cb = gtk_menu_item_new_with_label ("Color Balance"); + speed = gtk_menu_item_new_with_label ("Playback Speed"); media_info = gst_player_get_media_info (play->player); @@ -1144,6 +1186,8 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) G_CALLBACK (skip_prev_clicked_cb), play); g_signal_connect (G_OBJECT (info), "activate", G_CALLBACK (media_info_clicked_cb), play); + g_signal_connect (G_OBJECT (speed), "activate", + G_CALLBACK (player_speed_clicked_cb), play); g_signal_connect (G_OBJECT (quit), "activate", G_CALLBACK (player_quit_clicked_cb), play); @@ -1156,6 +1200,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); gtk_menu_shell_append (GTK_MENU_SHELL (menu), cb); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), speed); gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); gtk_widget_show_all (menu); From cce3ab6381bf291d1fde96aede8ad4a42ac38c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 16 Jun 2015 09:54:25 +0200 Subject: [PATCH 107/412] playback/player: gtk-play: Add close button to the playback speed dialog --- playback/player/gtk/gtk-play.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 96bb25d4aa..2a8cfa5c3a 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1066,9 +1066,10 @@ player_speed_clicked_cb (GtkButton * button, GtkPlay * play) GtkWidget *content; GtkWidget *rate_spinbutton; - dialog = gtk_dialog_new (); + dialog = + gtk_dialog_new_with_buttons ("Playback speed control", GTK_WINDOW (play), + GTK_DIALOG_DESTROY_WITH_PARENT, "_Close", GTK_RESPONSE_CLOSE, NULL); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (play)); - gtk_window_set_title (GTK_WINDOW (dialog), "Playback speed control"); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1); From b59a893da7c2e431e4d676b940adcd2bc7018e8a Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Fri, 26 Jun 2015 20:42:31 -0500 Subject: [PATCH 108/412] playback/player: gtk-play: add support to build UI through gtkbuilder - build toolbar ui from glade generated xml files - build media info dialog from glade generated xml files - add support to apply css style on widget - multiple cleanups --- playback/player/gtk/Makefile.am | 34 +- playback/player/gtk/gtk-play.c | 874 ++++++++---------- playback/player/gtk/resources/gresources.xml | 11 + .../player/gtk/resources/media_info_dialog.ui | 108 +++ playback/player/gtk/resources/toolbar.css | 26 + playback/player/gtk/resources/toolbar.ui | 341 +++++++ 6 files changed, 877 insertions(+), 517 deletions(-) create mode 100644 playback/player/gtk/resources/gresources.xml create mode 100644 playback/player/gtk/resources/media_info_dialog.ui create mode 100644 playback/player/gtk/resources/toolbar.css create mode 100644 playback/player/gtk/resources/toolbar.ui diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am index df035556eb..f7e6d0f58b 100644 --- a/playback/player/gtk/Makefile.am +++ b/playback/player/gtk/Makefile.am @@ -1,10 +1,36 @@ bin_PROGRAMS = gtk-play -gtk_play_SOURCES = gtk-play.c +gtk-play-resources.c: resources/gresources.xml \ + resources/media_info_dialog.ui \ + resources/toolbar.css \ + resources/toolbar.ui + $(AM_V_GEN) \ + glib-compile-resources \ + --sourcedir=$(srcdir)/resources \ + --target=$@ \ + --generate-source \ + --c-name as \ + $(srcdir)/resources/gresources.xml + +gtk-play-resources.h: resources/gresources.xml \ + resources/media_info_dialog.ui \ + resources/toolbar.css \ + resources/toolbar.ui + $(AM_V_GEN) \ + glib-compile-resources \ + --sourcedir=$(srcdir)/resources \ + --target=$@ \ + --generate-header \ + --c-name as \ + $(srcdir)/resources/gresources.xml + +BUILT_SOURCES: gtk-play-resources.c gtk-play-resources.h + +gtk_play_SOURCES = gtk-play.c gtk-play-resources.c LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ - $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) + $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) -AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) +AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) -noinst_HEADERS = +noinst_HEADERS = gtk-play-resources.h diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 2a8cfa5c3a..32973afe7f 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -42,6 +42,12 @@ #define APP_NAME "gtk-play" +#define TOOLBAR_GET_OBJECT(x) \ + (GtkWidget *)gtk_builder_get_object (play->toolbar_ui, #x) + +#define TOOLBAR_GET_LABEL(x) \ + (GtkLabel *) gtk_builder_get_object (play->toolbar_ui, #x) + typedef GtkApplication GtkPlayApp; typedef GtkApplicationClass GtkPlayAppClass; @@ -63,20 +69,22 @@ typedef struct GtkWidget *prev_button, *next_button; GtkWidget *seekbar; GtkWidget *video_area; - GtkWidget *image_area; GtkWidget *volume_button; - GtkWidget *media_info_button; - GtkWidget *repeat_button; GtkWidget *fullscreen_button; GtkWidget *toolbar; + GtkWidget *toolbar_overlay; + GtkWidget *media_info_dialog; + GtkLabel *title_label; + GtkLabel *elapshed_label; + GtkLabel *remain_label; + GtkLabel *rate_label; GdkCursor *default_cursor; - gulong seekbar_value_changed_signal_id; - GdkPixbuf *image_pixbuf; gboolean playing; gboolean loop; gboolean fullscreen; - gboolean visual; gint toolbar_hide_timeout; + + GtkBuilder *toolbar_ui; } GtkPlay; typedef GtkApplicationWindowClass GtkPlayClass; @@ -88,7 +96,6 @@ enum PROP_0, PROP_LOOP, PROP_FULLSCREEN, - PROP_VISUAL, PROP_URIS, LAST_PROP @@ -124,8 +131,6 @@ enum SUBTITLE_INFO_END, }; -static void display_cover_art (GtkPlay * play, GstPlayerMediaInfo * media_info); - static void set_title (GtkPlay * play, const gchar * title) { @@ -136,6 +141,25 @@ set_title (GtkPlay * play, const gchar * title) } } +static GtkBuilder * +load_from_builder (const gchar * filename, gboolean register_sig_handler, + GtkPlay * play) +{ + GtkBuilder *builder; + + builder = gtk_builder_new_from_resource (filename); + if (builder == NULL) { + g_print ("ERROR: failed to load %s \n", filename); + return NULL; + } + + if (register_sig_handler) + gtk_builder_connect_signals (builder, play); + + return builder; +} + + static void delete_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { @@ -162,15 +186,47 @@ video_area_realize_cb (GtkWidget * widget, GtkPlay * play) } static void -play_pause_clicked_cb (GtkButton * button, GtkPlay * play) +gtk_play_set_rate (GtkPlay * play, gdouble step) +{ + gdouble val; + + val = gst_player_get_rate (play->player); + val += step; + if (val == 0.0) + val = step; + gst_player_set_rate (play->player, val); + + if (val == 1.0) + gtk_label_set_label (play->rate_label, NULL); + else { + gchar *data; + + data = g_strdup_printf ("%.2fx", val); + gtk_label_set_label (play->rate_label, data); + g_free (data); + } +} + +G_MODULE_EXPORT void +rewind_button_clicked_cb (GtkButton * button, GtkPlay * play) +{ + gtk_play_set_rate (play, -0.5); +} + +G_MODULE_EXPORT void +forward_button_clicked_cb (GtkButton * button, GtkPlay * play) +{ + gtk_play_set_rate (play, 0.5); +} + +G_MODULE_EXPORT void +play_pause_button_clicked_cb (GtkButton * button, GtkPlay * play) { GtkWidget *image; if (play->playing) { gst_player_pause (play->player); - image = - gtk_image_new_from_icon_name ("media-playback-start", - GTK_ICON_SIZE_BUTTON); + image = TOOLBAR_GET_OBJECT (play_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; @@ -187,9 +243,7 @@ play_pause_clicked_cb (GtkButton * button, GtkPlay * play) GTK_WINDOW (play), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); gst_player_play (play->player); - image = - gtk_image_new_from_icon_name ("media-playback-pause", - GTK_ICON_SIZE_BUTTON); + image = TOOLBAR_GET_OBJECT (pause_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = TRUE; } @@ -199,13 +253,10 @@ static void play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) { /* reset the button/widget state to default */ - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - play->image_pixbuf = NULL; - gtk_widget_set_sensitive (play->media_info_button, FALSE); gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); gtk_widget_set_sensitive (play->prev_button, g_list_previous (uri) != NULL); gtk_widget_set_sensitive (play->next_button, g_list_next (uri) != NULL); + gtk_label_set_label (play->rate_label, NULL); /* set uri or suburi */ if (ext_suburi) @@ -231,8 +282,8 @@ play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) set_title (play, uri->data); } -static void -skip_prev_clicked_cb (GtkButton * button, GtkPlay * play) +G_MODULE_EXPORT void +prev_button_clicked_cb (GtkButton * button, GtkPlay * play) { GList *prev; @@ -388,8 +439,8 @@ open_file_clicked_cb (GtkWidget * unused, GtkPlay * play) } } -static void -skip_next_clicked_cb (GtkButton * button, GtkPlay * play) +G_MODULE_EXPORT void +next_button_clicked_cb (GtkButton * button, GtkPlay * play) { GList *next; @@ -519,51 +570,14 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) } } -static gboolean -is_current_stream (GtkPlay * play, GstPlayerStreamInfo * stream) -{ - gboolean ret = FALSE; - GstPlayerStreamInfo *s; - GstPlayerVideoInfo *video = gst_player_get_current_video_track (play->player); - GstPlayerAudioInfo *audio = gst_player_get_current_audio_track (play->player); - GstPlayerSubtitleInfo *sub = - gst_player_get_current_subtitle_track (play->player); - - if (GST_IS_PLAYER_VIDEO_INFO (stream)) - s = (GstPlayerStreamInfo *) video; - else if (GST_IS_PLAYER_AUDIO_INFO (stream)) - s = (GstPlayerStreamInfo *) audio; - else - s = (GstPlayerStreamInfo *) sub; - - if (s) - if (gst_player_stream_info_get_index (stream) == - gst_player_stream_info_get_index (s)) - ret = TRUE; - - if (audio) - g_object_unref (audio); - - if (video) - g_object_unref (video); - - if (sub) - g_object_unref (sub); - - return ret; -} - -static GtkTreeModel * -create_and_fill_model (GtkPlay * play, GstPlayerMediaInfo * info) +static void +fill_tree_model (GtkTreeStore * tree, GtkPlay * play, GstPlayerMediaInfo * info) { GList *l; guint count; - GtkTreeStore *tree; GtkTreeIter child, parent; count = 0; - tree = gtk_tree_store_new (COL_NUM, G_TYPE_STRING); - for (l = gst_player_media_info_get_stream_list (info); l != NULL; l = l->next) { gchar *buffer; gint i, start, end; @@ -581,8 +595,7 @@ create_and_fill_model (GtkPlay * play, GstPlayerMediaInfo * info) end = SUBTITLE_INFO_END; } - buffer = g_strdup_printf ("Stream %u %s", count++, - is_current_stream (play, stream) ? "(current)" : ""); + buffer = g_strdup_printf ("Stream %u", count++); gtk_tree_store_append (tree, &parent, NULL); gtk_tree_store_set (tree, &parent, COL_TEXT, buffer, -1); g_free (buffer); @@ -602,131 +615,74 @@ create_and_fill_model (GtkPlay * play, GstPlayerMediaInfo * info) } } } - - return GTK_TREE_MODEL (tree); } -static GtkWidget * -create_view_and_model (GtkPlay * play, GstPlayerMediaInfo * info) +G_MODULE_EXPORT void +media_info_dialog_button_clicked_cb (GtkButton * button, GtkPlay * play) { + gtk_widget_destroy (GTK_WIDGET (play->media_info_dialog)); + play->media_info_dialog = NULL; +} + +static void +media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) +{ + GtkBuilder *dialog_ui; GtkWidget *view; + GtkTreeStore *tree; GtkTreeModel *model; GtkTreeViewColumn *col; GtkCellRenderer *renderer; - view = gtk_tree_view_new (); - col = gtk_tree_view_column_new (); - gtk_tree_view_append_column (GTK_TREE_VIEW (view), col); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + dialog_ui = load_from_builder ("/ui/media_info_dialog.ui", TRUE, play); + if (!dialog_ui) + return; + play->media_info_dialog = + (GtkWidget *) gtk_builder_get_object (dialog_ui, "media_info_dialog"); + gtk_window_set_transient_for (GTK_WINDOW (play->media_info_dialog), + GTK_WINDOW (play)); + + view = (GtkWidget *) gtk_builder_get_object (dialog_ui, "view"); + col = (GtkTreeViewColumn *) gtk_builder_get_object (dialog_ui, "col"); + + /* TODO: use glade cell renderer (not working for me) */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, renderer, TRUE); gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT); - model = create_and_fill_model (play, info); - gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); - g_object_unref (model); + tree = (GtkTreeStore *) gtk_builder_get_object (dialog_ui, "tree"); + fill_tree_model (tree, play, media_info); - return view; -} - -static void -delete_media_info_window (GtkWidget * button, GtkWindow * window) -{ - gtk_window_close (window); -} - -static void -create_media_info_window (GtkPlay * play, GstPlayerMediaInfo * info) -{ - GtkWidget *sw; - GtkWidget *vbox; - GtkWidget *label; - GtkWidget *view; - GtkWidget *hbox; - GtkWidget *uri; - GtkWidget *loc; - GtkTextIter iter; - GtkWidget *window; - GtkTextBuffer *buffer; - GtkWidget *hbox_close; - GtkWidget *button_close; - - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW (window), "Media information"); - gtk_window_set_default_size (GTK_WINDOW (window), 550, 450); - gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); - gtk_container_set_border_width (GTK_CONTAINER (window), 10); - gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (play)); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); - gtk_container_add (GTK_CONTAINER (window), vbox); - - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), - "Information about all the streams contains in your media. \n" - "Current selected streams are marked as (current)."); - gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 2); - - sw = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), - GTK_SHADOW_ETCHED_IN); - gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); - - view = create_view_and_model (play, info); - gtk_container_add (GTK_CONTAINER (sw), view); g_signal_connect (view, "realize", G_CALLBACK (gtk_tree_view_expand_all), NULL); + + gtk_widget_set_size_request (play->media_info_dialog, 550, 450); - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); - gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); - - loc = gtk_label_new ("Location : "); - gtk_box_pack_start (GTK_BOX (hbox), loc, FALSE, FALSE, 2); - - buffer = gtk_text_buffer_new (NULL); - gtk_text_buffer_get_start_iter (buffer, &iter); - gtk_text_buffer_insert (buffer, &iter, - gst_player_media_info_get_uri (info), -1); - uri = gtk_text_view_new_with_buffer (buffer); - gtk_box_pack_start (GTK_BOX (hbox), uri, FALSE, FALSE, 2); - gtk_text_view_set_editable (GTK_TEXT_VIEW (uri), FALSE); - g_object_unref (buffer); - - hbox_close = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); - gtk_box_pack_start (GTK_BOX (vbox), hbox_close, FALSE, FALSE, 2); - button_close = gtk_button_new_with_label (" Close "); - g_signal_connect (button_close, "clicked", - G_CALLBACK (delete_media_info_window), window); - gtk_box_pack_end (GTK_BOX (hbox_close), button_close, FALSE, FALSE, 3); - - gtk_widget_show_all (window); + gtk_widget_show_all (play->media_info_dialog); + gtk_dialog_run (GTK_DIALOG (play->media_info_dialog)); } static void media_info_clicked_cb (GtkButton * button, GtkPlay * play) { - GstPlayerMediaInfo *info; + GstPlayerMediaInfo *media_info; - info = gst_player_get_media_info (play->player); - if (!info) + media_info = gst_player_get_media_info (play->player); + if (!media_info) return; - create_media_info_window (play, info); - - g_object_unref (info); + media_info_dialog (play, media_info); + g_object_unref (media_info); } static gboolean -toolbar_hide_func (GtkPlay * play) +toolbar_hide_cb (GtkPlay * play) { GdkCursor *cursor; - /* TODO: add some animation while hiding the toolbar. */ + /* hide mouse pointer and toolbar */ gtk_widget_hide (play->toolbar); - - /* hide the mouse pointer */ cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (play)), GDK_BLANK_CURSOR); @@ -739,43 +695,59 @@ toolbar_hide_func (GtkPlay * play) } static void -fullscreen_toggle_cb (GtkToggleButton * widget, GtkPlay * play) +toolbar_show (GtkPlay * play) +{ + /* if timer is running then kill it */ + if (play->toolbar_hide_timeout) { + g_source_remove (play->toolbar_hide_timeout); + play->toolbar_hide_timeout = 0; + } + + /* show toolbar and mouse pointer */ + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET + (play->video_area)), play->default_cursor); + gtk_widget_show (play->toolbar); +} + +static void +start_toolbar_hide_timer (GtkPlay * play) +{ + /* hide toolbar only if its playing */ + if (!play->playing) + return; + + /* start timer to hide toolbar */ + if (play->toolbar_hide_timeout) + g_source_remove (play->toolbar_hide_timeout); + play->toolbar_hide_timeout = g_timeout_add_seconds (5, + (GSourceFunc) toolbar_hide_cb, play); +} + +G_MODULE_EXPORT void +fullscreen_button_toggled_cb (GtkToggleButton * widget, GtkPlay * play) { GtkWidget *image; if (gtk_toggle_button_get_active (widget)) { - image = gtk_image_new_from_icon_name ("view-restore", GTK_ICON_SIZE_BUTTON); + image = TOOLBAR_GET_OBJECT (restore_image); gtk_window_fullscreen (GTK_WINDOW (play)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); - - /* start timer to hide toolbar */ - if (play->toolbar_hide_timeout) - g_source_remove (play->toolbar_hide_timeout); - play->toolbar_hide_timeout = g_timeout_add_seconds (5, - (GSourceFunc) toolbar_hide_func, play); } else { - /* if toolbar hide timer is running then kill it */ - if (play->toolbar_hide_timeout) { - g_source_remove (play->toolbar_hide_timeout); - play->toolbar_hide_timeout = 0; - } - - image = gtk_image_new_from_icon_name ("view-fullscreen", - GTK_ICON_SIZE_BUTTON); + image = TOOLBAR_GET_OBJECT (fullscreen_image); gtk_window_unfullscreen (GTK_WINDOW (play)); gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); } } -static void +G_MODULE_EXPORT void seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) { gdouble value = gtk_range_get_value (GTK_RANGE (play->seekbar)); gst_player_seek (play->player, gst_util_uint64_scale (value, GST_SECOND, 1)); } -void -volume_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) +G_MODULE_EXPORT void +volume_button_value_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) { gst_player_set_volume (play->player, value); } @@ -860,7 +832,6 @@ disable_track (GtkPlay * play, GType type) { if (type == GST_TYPE_PLAYER_VIDEO_INFO) { gst_player_set_video_track_enabled (play->player, FALSE); - display_cover_art (play, NULL); /* display cover art */ } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) gst_player_set_audio_track_enabled (play->player, FALSE); else @@ -873,11 +844,6 @@ change_track (GtkPlay * play, gint index, GType type) if (type == GST_TYPE_PLAYER_VIDEO_INFO) { gst_player_set_video_track (play->player, index); gst_player_set_video_track_enabled (play->player, TRUE); - /* if video area widget is not visible then make it visible */ - if (!gtk_widget_is_visible (play->video_area)) { - gtk_widget_hide (play->image_area); - gtk_widget_show (play->video_area); - } } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) { gst_player_set_audio_track (play->player, index); gst_player_set_audio_track_enabled (play->player, TRUE); @@ -912,18 +878,9 @@ visualization_changed_cb (GtkWidget * widget, GtkPlay * play) gchar *name; if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { - - /* video_area is window-id is shared with playbin hence - * video_area widget will be used by visualization elements to - * render the visuals. If visualization is enabled then hide - * image widget and show video widget and similiarly when visualization - * is disabled then hide video widget and show imag widget. - */ name = g_object_get_data (G_OBJECT (widget), "name"); if (g_strcmp0 (name, "disable") == 0) { gst_player_set_visualization_enabled (play->player, FALSE); - gtk_widget_hide (play->video_area); - gtk_widget_show (play->image_area); } else { const gchar *vis_name; @@ -932,8 +889,6 @@ visualization_changed_cb (GtkWidget * widget, GtkPlay * play) if (!(vis_name = gst_player_get_current_visualization (play->player))) { gst_player_set_visualization_enabled (play->player, TRUE); } - gtk_widget_hide (play->image_area); - gtk_widget_show (play->video_area); } } } @@ -1057,41 +1012,6 @@ player_set_rate_cb (GtkSpinButton * button, GtkPlay * play) gst_player_set_rate (play->player, gtk_spin_button_get_value (button)); } -static void -player_speed_clicked_cb (GtkButton * button, GtkPlay * play) -{ - GtkWidget *box; - GtkWidget *label; - GtkWidget *dialog; - GtkWidget *content; - GtkWidget *rate_spinbutton; - - dialog = - gtk_dialog_new_with_buttons ("Playback speed control", GTK_WINDOW (play), - GTK_DIALOG_DESTROY_WITH_PARENT, "_Close", GTK_RESPONSE_CLOSE, NULL); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (play)); - - content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 1); - gtk_box_pack_start (GTK_BOX (content), box, TRUE, TRUE, 0); - - label = gtk_label_new ("Playback rate"); - rate_spinbutton = gtk_spin_button_new_with_range (-64, 64, 0.1); - gtk_spin_button_set_digits (GTK_SPIN_BUTTON (rate_spinbutton), 2); - gtk_spin_button_set_value (GTK_SPIN_BUTTON (rate_spinbutton), - gst_player_get_rate (play->player)); - - gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 2); - gtk_box_pack_start (GTK_BOX (box), rate_spinbutton, TRUE, TRUE, 2); - g_signal_connect (rate_spinbutton, "value-changed", - G_CALLBACK (player_set_rate_cb), play); - - gtk_widget_show_all (dialog); - gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); -} - static void player_quit_clicked_cb (GtkButton * button, GtkPlay * play) { @@ -1113,7 +1033,6 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *submenu; GtkWidget *vis; GtkWidget *cb; - GtkWidget *speed; GstPlayerMediaInfo *media_info; menu = gtk_menu_new (); @@ -1127,7 +1046,6 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) quit = gtk_menu_item_new_with_label ("Quit"); vis = gtk_menu_item_new_with_label ("Visualization"); cb = gtk_menu_item_new_with_label ("Color Balance"); - speed = gtk_menu_item_new_with_label ("Playback Speed"); media_info = gst_player_get_media_info (play->player); @@ -1182,13 +1100,11 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) g_signal_connect (G_OBJECT (cb), "activate", G_CALLBACK (color_balance_clicked_cb), play); g_signal_connect (G_OBJECT (next), "activate", - G_CALLBACK (skip_next_clicked_cb), play); + G_CALLBACK (next_button_clicked_cb), play); g_signal_connect (G_OBJECT (prev), "activate", - G_CALLBACK (skip_prev_clicked_cb), play); + G_CALLBACK (prev_button_clicked_cb), play); g_signal_connect (G_OBJECT (info), "activate", G_CALLBACK (media_info_clicked_cb), play); - g_signal_connect (G_OBJECT (speed), "activate", - G_CALLBACK (player_speed_clicked_cb), play); g_signal_connect (G_OBJECT (quit), "activate", G_CALLBACK (player_quit_clicked_cb), play); @@ -1201,7 +1117,6 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_menu_shell_append (GTK_MENU_SHELL (menu), sub); gtk_menu_shell_append (GTK_MENU_SHELL (menu), info); gtk_menu_shell_append (GTK_MENU_SHELL (menu), cb); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), speed); gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); gtk_widget_show_all (menu); @@ -1233,68 +1148,108 @@ mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, } static gboolean -image_area_draw_cb (GtkWidget * widget, cairo_t * cr, GtkPlay * play) +video_area_leave_notify_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { - if (play->image_pixbuf) { - gint width, height; - gint pix_width, pix_height; - gint x = 0, y = 0; - gdouble scalex = 0.0, scaley = 0.0; + start_toolbar_hide_timer (play); - width = gtk_widget_get_allocated_width (widget); - height = gtk_widget_get_allocated_height (widget); - pix_width = gdk_pixbuf_get_width (play->image_pixbuf); - pix_height = gdk_pixbuf_get_height (play->image_pixbuf); - - /* if image is bigger than widget then scale down otherwise center it. */ - if (width <= pix_width) - scalex = (gdouble) width / (gdouble) pix_width; - else - x = (width - pix_width) / 2; - if (height <= pix_height) - scaley = (gdouble) height / (gdouble) pix_height; - else - y = (height - pix_height) / 2; - - /* fill background with black */ - cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); - cairo_rectangle (cr, 0, 0, width, height); - cairo_fill (cr); - - if (scalex > 0.0 && scaley > 0.0) - cairo_scale (cr, scalex, scaley); - - gdk_cairo_set_source_pixbuf (cr, play->image_pixbuf, x, y); - cairo_paint (cr); - } else { - /* fill background with black */ - cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); - cairo_paint (cr); - } - - return FALSE; + return TRUE; } static gboolean -gtk_show_toolbar_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +video_area_toolbar_show_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { - if (gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON (play->fullscreen_button))) { + toolbar_show (play); - /* if timer is running then kill it */ - if (play->toolbar_hide_timeout) { - g_source_remove (play->toolbar_hide_timeout); - play->toolbar_hide_timeout = 0; - } + start_toolbar_hide_timer (play); - /* show mouse pointer */ - gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET - (play->video_area)), play->default_cursor); + return TRUE; +} - gtk_widget_show (play->toolbar); - play->toolbar_hide_timeout = g_timeout_add_seconds (5, - (GSourceFunc) toolbar_hide_func, play); +static gboolean +overlay_leave_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +{ + start_toolbar_hide_timer (play); + + return TRUE; +} + +static gboolean +overlay_enter_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +{ + toolbar_show (play); + + return TRUE; +} + +static void +apply_css (GtkWidget *widget, GtkStyleProvider *provider) +{ + gtk_style_context_add_provider (gtk_widget_get_style_context (widget), + provider, G_MAXUINT); + if (GTK_IS_CONTAINER (widget)) { + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) apply_css, provider); } +} + +static void +gtk_widget_apply_css (GtkWidget * widget, const gchar * filename) +{ + GBytes *bytes; + gsize data_size; + const guint8 *data; + GError *err = NULL; + GtkStyleProvider *provider; + + if (widget == NULL) + return; + + provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); + bytes = g_resources_lookup_data (filename, 0, &err); + if (err) { + g_print ("ERROR: failed to apply css %s '%s' \n", filename, err->message); + return; + } + data = g_bytes_get_data (bytes, &data_size); + gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), + (gchar *)data, data_size, NULL); + g_bytes_unref (bytes); + + apply_css (widget, provider); +} + +static gboolean +get_child_position (GtkOverlay * overlay, GtkWidget * widget, + GtkAllocation * alloc, GtkPlay * play) +{ + GtkRequisition req; + GtkWidget *child; + GtkAllocation main_alloc; + gint x, y; + GtkWidget * relative = play->video_area; + + child = gtk_bin_get_child (GTK_BIN (overlay)); + gtk_widget_translate_coordinates (relative, child, 0, 0, &x, &y); + main_alloc.x = x; + main_alloc.y = y; + main_alloc.width = gtk_widget_get_allocated_width (relative); + main_alloc.height = gtk_widget_get_allocated_height (relative); + + gtk_widget_get_preferred_size (widget, NULL, &req); + + alloc->x = (main_alloc.width - req.width) / 2; + if (alloc->x < 0) + alloc->x = 0; + alloc->width = MIN (main_alloc.width, req.width); + if (gtk_widget_get_halign (widget) == GTK_ALIGN_END) + alloc->x += main_alloc.width - req.width; + + alloc->y = main_alloc.height - req.height - 20; + if (alloc->y < 0) + alloc->y = 0; + alloc->height = MIN (main_alloc.height, req.height); + if (gtk_widget_get_valign (widget) == GTK_ALIGN_END) + alloc->y += main_alloc.height - req.height; return TRUE; } @@ -1303,7 +1258,7 @@ static void create_ui (GtkPlay * play) { GtkWidget *image; - GtkWidget *controls, *main_hbox, *main_vbox; + GtkWidget *main_hbox, *main_vbox; GstElement *playbin, *gtk_sink; gtk_window_set_default_size (GTK_WINDOW (play), 640, 480); @@ -1336,140 +1291,123 @@ create_ui (GtkPlay * play) g_signal_connect (play->video_area, "realize", G_CALLBACK (video_area_realize_cb), play); } - g_signal_connect (play->video_area, "button-press-event", - G_CALLBACK (mouse_button_pressed_cb), play); - g_signal_connect (play->video_area, "motion-notify-event", - G_CALLBACK (gtk_show_toolbar_cb), play); - g_signal_connect (play->video_area, "scroll-event", - G_CALLBACK (gtk_show_toolbar_cb), play); gtk_widget_set_events (play->video_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK); - - play->image_area = gtk_drawing_area_new (); - g_signal_connect (play->image_area, "button-press-event", + g_signal_connect (play->video_area, "motion-notify-event", + G_CALLBACK (video_area_toolbar_show_cb), play); + g_signal_connect (play->video_area, "scroll-event", + G_CALLBACK (video_area_toolbar_show_cb), play); + g_signal_connect (play->video_area, "button-press-event", G_CALLBACK (mouse_button_pressed_cb), play); - g_signal_connect (play->image_area, "draw", - G_CALLBACK (image_area_draw_cb), play); - g_signal_connect (play->image_area, "motion-notify-event", - G_CALLBACK (gtk_show_toolbar_cb), play); - g_signal_connect (play->image_area, "scroll-event", - G_CALLBACK (gtk_show_toolbar_cb), play); - gtk_widget_set_events (play->image_area, GDK_EXPOSURE_MASK + g_signal_connect (play->video_area, "leave-notify-event", + G_CALLBACK (video_area_leave_notify_cb), play); + + /* load toolbar UI */ + play->toolbar_ui = load_from_builder ("/ui/toolbar.ui", TRUE, play); + if (!play->toolbar_ui) + return; + + play->toolbar = TOOLBAR_GET_OBJECT (toolbar); + play->play_pause_button = TOOLBAR_GET_OBJECT (play_pause_button); + play->seekbar = TOOLBAR_GET_OBJECT (seekbar); + play->next_button = TOOLBAR_GET_OBJECT (next_button); + play->prev_button = TOOLBAR_GET_OBJECT (prev_button); + play->fullscreen_button = TOOLBAR_GET_OBJECT (fullscreen_button); + play->volume_button = TOOLBAR_GET_OBJECT (volume_button); + play->elapshed_label = TOOLBAR_GET_LABEL (elapshed_time); + play->remain_label = TOOLBAR_GET_LABEL (remain_time); + play->rate_label = TOOLBAR_GET_LABEL (rate_label); + play->title_label = TOOLBAR_GET_LABEL (title_label); + + main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); + + /* set minimum window size */ + gtk_widget_set_size_request (main_hbox, 320, 240); + + /* set the toolbar size */ + gtk_widget_set_size_request (play->toolbar, 500, 50); + + play->toolbar_overlay = gtk_overlay_new (); + gtk_overlay_add_overlay (GTK_OVERLAY (play->toolbar_overlay), play->toolbar); + gtk_container_add (GTK_CONTAINER (play->toolbar_overlay), main_hbox); + gtk_container_add (GTK_CONTAINER (play), play->toolbar_overlay); + gtk_widget_set_events (play->toolbar_overlay, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK); - /* Unified play/pause button */ - play->play_pause_button = - gtk_button_new_from_icon_name ("media-playback-pause", - GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->play_pause_button), "clicked", - G_CALLBACK (play_pause_clicked_cb), play); + g_signal_connect (play->toolbar_overlay, "get-child-position", + G_CALLBACK (get_child_position), play); + g_signal_connect (play->toolbar_overlay, "leave-notify-event", + G_CALLBACK (overlay_leave_notify_event_cb), play); + g_signal_connect (play->toolbar_overlay, "enter-notify-event", + G_CALLBACK (overlay_enter_notify_event_cb), play); - play->seekbar = - gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); - gtk_scale_set_draw_value (GTK_SCALE (play->seekbar), 0); - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); - play->seekbar_value_changed_signal_id = - g_signal_connect (G_OBJECT (play->seekbar), "value-changed", - G_CALLBACK (seekbar_value_changed_cb), play); - - /* Skip backward button */ - play->prev_button = - gtk_button_new_from_icon_name ("media-skip-backward", - GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->prev_button), "clicked", - G_CALLBACK (skip_prev_clicked_cb), play); - gtk_widget_set_sensitive (play->prev_button, FALSE); - - /* Skip forward button */ - play->next_button = - gtk_button_new_from_icon_name ("media-skip-forward", - GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->next_button), "clicked", - G_CALLBACK (skip_next_clicked_cb), play); - gtk_widget_set_sensitive (play->next_button, FALSE); - - /* Playlist repeat button */ - play->repeat_button = gtk_toggle_button_new (); - image = gtk_image_new_from_icon_name ("media-playlist-repeat", - GTK_ICON_SIZE_BUTTON); - gtk_button_set_image (GTK_BUTTON (play->repeat_button), image); - if (play->loop) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->repeat_button), - TRUE); - - /* Volume control button */ - play->volume_button = gtk_volume_button_new (); - gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), - gst_player_get_volume (play->player)); - g_signal_connect (G_OBJECT (play->volume_button), "value-changed", - G_CALLBACK (volume_changed_cb), play); - - /* media information button */ - play->media_info_button = gtk_button_new_from_icon_name ("dialog-information", - GTK_ICON_SIZE_BUTTON); - g_signal_connect (G_OBJECT (play->media_info_button), "clicked", - G_CALLBACK (media_info_clicked_cb), play); - gtk_widget_set_sensitive (play->media_info_button, FALSE); - - /* Full screen button */ - play->fullscreen_button = gtk_toggle_button_new (); - image = gtk_image_new_from_icon_name ("view-fullscreen", - GTK_ICON_SIZE_BUTTON); - gtk_button_set_image (GTK_BUTTON (play->fullscreen_button), image); - g_signal_connect (G_OBJECT (play->fullscreen_button), "toggled", - G_CALLBACK (fullscreen_toggle_cb), play); - if (play->fullscreen) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->fullscreen_button), - TRUE); - - play->toolbar = controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start (GTK_BOX (controls), play->prev_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->play_pause_button, FALSE, - FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->next_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->repeat_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->seekbar, TRUE, TRUE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->volume_button, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->media_info_button, - FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (controls), play->fullscreen_button, - FALSE, FALSE, 2); - - main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start (GTK_BOX (main_hbox), play->video_area, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (main_hbox), play->image_area, TRUE, TRUE, 0); - - main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (main_vbox), controls, FALSE, FALSE, 0); - gtk_container_add (GTK_CONTAINER (play), main_vbox); + /* apply css on widgets */ + gtk_widget_apply_css (play->toolbar, "/css/toolbar.css"); if (!gtk_sink) gtk_widget_realize (play->video_area); gtk_widget_hide (play->video_area); + + /* start toolbar autohide timer */ + start_toolbar_hide_timer (play); + + /* check if we need to enable fullscreen */ + if (play->fullscreen) + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); + + /* enable visualization (by default laybin uses goom) */ + /* if visualization is enabled then use the first element */ + gst_player_set_visualization_enabled (play->player, TRUE); } static void duration_changed_cb (GstPlayer * unused, GstClockTime duration, GtkPlay * play) { - gtk_range_set_range (GTK_RANGE (play->seekbar), 0, + gtk_range_set_range (GTK_RANGE (play->seekbar), 0.0, (gdouble) duration / GST_SECOND); } +static void +update_position_label (GtkLabel * label, guint64 seconds) +{ + gchar *data; + gint hrs, mins; + + hrs = seconds / 3600; + seconds -= hrs * 3600; + mins = seconds / 60; + seconds -= mins * 60; + + if (hrs) + data = g_strdup_printf ("%02d:%02d", hrs, mins); + else + data = g_strdup_printf ("%02d:%02ld", mins, seconds); + + gtk_label_set_label (label, data); + g_free (data); +} + static void position_updated_cb (GstPlayer * unused, GstClockTime position, GtkPlay * play) { - g_signal_handler_block (play->seekbar, play->seekbar_value_changed_signal_id); + g_signal_handlers_block_by_func (play->seekbar, + seekbar_value_changed_cb, play); gtk_range_set_value (GTK_RANGE (play->seekbar), (gdouble) position / GST_SECOND); - g_signal_handler_unblock (play->seekbar, - play->seekbar_value_changed_signal_id); + update_position_label (play->elapshed_label, position / GST_SECOND); + update_position_label (play->remain_label, + GST_CLOCK_DIFF (position, gst_player_get_duration (play->player)) / + GST_SECOND); + g_signal_handlers_unblock_by_func (play->seekbar, + seekbar_value_changed_cb, play); } static void @@ -1479,8 +1417,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) GList *next = NULL; next = g_list_next (play->current_uri); - if (!next && gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON (play->repeat_button))) + if (!next && play->loop) next = g_list_first (play->uris); if (next) { @@ -1489,9 +1426,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) GtkWidget *image; gst_player_pause (play->player); - image = - gtk_image_new_from_icon_name ("media-playback-start", - GTK_ICON_SIZE_BUTTON); + image = TOOLBAR_GET_OBJECT (play_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; if (play->inhibit_cookie) @@ -1502,31 +1437,23 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } } -static gboolean -_has_active_stream (GtkPlay * play, void *(*func) (GstPlayer * player)) -{ - void *obj; - - obj = func (play->player); - if (obj) { - g_object_unref (obj); - return TRUE; - } - - return FALSE; -} - static GdkPixbuf * -gst_sample_to_pixbuf (GtkPlay * play, GstSample * sample) +gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) { - GdkPixbufLoader *loader; - GdkPixbuf *pixbuf = NULL; - GError *err = NULL; + GstSample *sample; GstMapInfo info; GstBuffer *buffer; + GError *err = NULL; + GdkPixbufLoader *loader; + GdkPixbuf *pixbuf = NULL; const GstStructure *caps_struct; GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED; + /* get image sample buffer from media */ + sample = gst_player_media_info_get_image_sample (media_info); + if (!sample) + return NULL; + buffer = gst_sample_get_buffer (sample); caps_struct = gst_sample_get_info (sample); @@ -1566,84 +1493,27 @@ gst_sample_to_pixbuf (GtkPlay * play, GstSample * sample) return pixbuf; } - -static void -display_cover_art (GtkPlay * play, GstPlayerMediaInfo * media_info) -{ - GstSample *sample; - GstPlayerMediaInfo *temp_media_info = NULL; - - /* hide the video widget and show image widget */ - gtk_widget_hide (play->video_area); - gtk_widget_show (play->image_area); - - /* if media information is not passed then get it from player */ - if (!media_info) - temp_media_info = media_info = gst_player_get_media_info (play->player); - - sample = gst_player_media_info_get_image_sample (media_info); - if (!sample) - goto cleanup; - - if (play->image_pixbuf) - g_object_unref (play->image_pixbuf); - - play->image_pixbuf = gst_sample_to_pixbuf (play, sample); - -cleanup: - gtk_widget_queue_draw (play->image_area); /* send expose event to widget */ - - if (temp_media_info) - g_object_unref (temp_media_info); -} - -static gboolean -has_active_stream (GtkPlay * play, GType type) -{ - if (type == GST_TYPE_PLAYER_VIDEO_INFO) - return _has_active_stream (play, - (void *) gst_player_get_current_video_track); - else if (type == GST_TYPE_PLAYER_AUDIO_INFO) - return _has_active_stream (play, - (void *) gst_player_get_current_audio_track); - else - return _has_active_stream (play, - (void *) gst_player_get_current_subtitle_track); -} - static void media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, GtkPlay * play) { - if (!gtk_widget_is_sensitive (play->media_info_button)) { - const gchar *title; - const gchar *vis; + const gchar *title; + GdkPixbuf *pixbuf; - title = gst_player_media_info_get_title (media_info); - if (title) - set_title (play, title); + title = gst_player_media_info_get_title (media_info); + if (!title) + title = g_path_get_basename (gst_player_media_info_get_uri (media_info)); - gtk_widget_set_sensitive (play->media_info_button, TRUE); + pixbuf = gtk_play_get_cover_image (media_info); - /* if we have active video stream then hide image widget - * and show video widget otherwise show the cover art. - */ - if (has_active_stream (play, GST_TYPE_PLAYER_VIDEO_INFO)) { - gtk_widget_hide (play->image_area); - gtk_widget_show (play->video_area); - } else { - display_cover_art (play, media_info); - } + if (title) { + gtk_label_set_label (play->title_label, title); + set_title (play, title); + } - /* if we have audio only stream and visualization is enabled - * then show video widget. - */ - vis = gst_player_get_current_visualization (play->player); - if (!has_active_stream (play, GST_TYPE_PLAYER_VIDEO_INFO) && - has_active_stream (play, GST_TYPE_PLAYER_AUDIO_INFO) && vis) { - gtk_widget_show (play->video_area); - gtk_widget_hide (play->image_area); - } + if (pixbuf) { + gtk_window_set_icon (GTK_WINDOW (play), pixbuf); + g_object_unref (pixbuf); } } @@ -1657,11 +1527,11 @@ player_volume_changed_cb (GstPlayer * player, GtkPlay * play) if (fabs (cur_val - new_val) > 0.001) { g_signal_handlers_block_by_func (play->volume_button, - volume_changed_cb, play); + volume_button_value_changed_cb, play); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), new_val); g_signal_handlers_unblock_by_func (play->volume_button, - volume_changed_cb, play); + volume_button_value_changed_cb, play); } } @@ -1678,9 +1548,6 @@ gtk_play_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_FULLSCREEN: self->fullscreen = g_value_get_boolean (value); break; - case PROP_VISUAL: - self->visual = g_value_get_boolean (value); - break; case PROP_URIS: self->uris = g_value_get_pointer (value); break; @@ -1725,19 +1592,6 @@ gtk_play_constructor (GType type, guint n_construct_params, create_ui (self); - /* if visualization is enabled then use the first element */ - if (self->visual) { - GstPlayerVisualization **viss; - viss = gst_player_visualizations_get (); - - if (viss && *viss) { - gst_player_set_visualization (self->player, (*viss)->name); - gst_player_set_visualization_enabled (self->player, TRUE); - } - if (viss) - gst_player_visualizations_free (viss); - } - g_signal_connect (self->player, "position-updated", G_CALLBACK (position_updated_cb), self); g_signal_connect (self->player, "duration-changed", @@ -1801,9 +1655,6 @@ gtk_play_class_init (GtkPlayClass * klass) g_param_spec_boolean ("fullscreen", "Fullscreen", "Fullscreen mode", FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); - gtk_play_properties[PROP_VISUAL] = - g_param_spec_boolean ("visual", "Visual", "Use Visualizations", FALSE, - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); gtk_play_properties[PROP_URIS] = g_param_spec_pointer ("uris", "URIs", "URIs to play", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); @@ -1819,13 +1670,12 @@ gtk_play_app_command_line (GApplication * application, GVariantDict *options; GtkPlay *play; GList *uris = NULL; - gboolean loop = FALSE, visual = FALSE, fullscreen = FALSE; + gboolean loop = FALSE, fullscreen = FALSE; gchar **uris_array = NULL; options = g_application_command_line_get_options_dict (command_line); g_variant_dict_lookup (options, "loop", "b", &loop); - g_variant_dict_lookup (options, "visual", "b", &visual); g_variant_dict_lookup (options, "fullscreen", "b", &fullscreen); g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&ay", &uris_array); @@ -1850,7 +1700,7 @@ gtk_play_app_command_line (GApplication * application, play = g_object_new (gtk_play_get_type (), "loop", loop, "fullscreen", - fullscreen, "visual", visual, "uris", uris, NULL); + fullscreen, "uris", uris, NULL); gtk_widget_show_all (GTK_WIDGET (play)); return @@ -1881,8 +1731,6 @@ gtk_play_app_new (void) {"loop", 'l', 0, G_OPTION_ARG_NONE, NULL, "Repeat all"}, {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, NULL, "Show the player in fullscreen"}, - {"visual", 'v', 0, G_OPTION_ARG_NONE, NULL, - "Show visualization when there is no video stream"}, {NULL} }; diff --git a/playback/player/gtk/resources/gresources.xml b/playback/player/gtk/resources/gresources.xml new file mode 100644 index 0000000000..90b3c43e86 --- /dev/null +++ b/playback/player/gtk/resources/gresources.xml @@ -0,0 +1,11 @@ + + + + toolbar.ui + media_info_dialog.ui + + + toolbar.css + + + diff --git a/playback/player/gtk/resources/media_info_dialog.ui b/playback/player/gtk/resources/media_info_dialog.ui new file mode 100644 index 0000000000..fe25cf11a9 --- /dev/null +++ b/playback/player/gtk/resources/media_info_dialog.ui @@ -0,0 +1,108 @@ + + + + + + + + + + + + media_info_dialog + False + center-on-parent + dialog + + + False + vertical + 2 + + + False + end + + + Close + media_info_dialog_button + True + True + True + + + + True + True + 0 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + 5 + Information about all the streams contains in your media. + + + False + True + 0 + + + + + True + True + 5 + in + + + True + True + tree + False + + + + + + autosize + col + + + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + diff --git a/playback/player/gtk/resources/toolbar.css b/playback/player/gtk/resources/toolbar.css new file mode 100644 index 0000000000..b9674542d2 --- /dev/null +++ b/playback/player/gtk/resources/toolbar.css @@ -0,0 +1,26 @@ +* { + background-color: rgba(43,56,54,0.0); + padding: 0px; +} + +GtkLabel { + font-size: 10px; + color: #ffffff; +} + +#toolbar { + border-radius: 25px; + border: 3px solid #212B2A; + background: rgba(43,56,54,0.6); +} + +GtkButton:hover { + border-radius: 45px; + background: rgba(43,56,54,1.0); + border: 1px solid #212B2A; +} + +#title_label { + font-size: 12px; +} + diff --git a/playback/player/gtk/resources/toolbar.ui b/playback/player/gtk/resources/toolbar.ui new file mode 100644 index 0000000000..c185795c11 --- /dev/null +++ b/playback/player/gtk/resources/toolbar.ui @@ -0,0 +1,341 @@ + + + + + + + True + False + 30 + gtk-media-forward + 0 + + + True + False + 25 + view-fullscreen + 0 + + + True + False + 30 + gtk-media-next + 0 + + + True + False + 35 + media-playback-pause + 0 + + + True + False + 0 + 0 + 35 + gtk-media-play + 0 + + + True + False + 30 + gtk-media-previous + + + restore_image + True + False + 25 + view-restore + 0 + + + True + False + 30 + gtk-media-rewind + + + toolbar + True + False + 5 + vertical + + + True + False + 20 + 20 + 9 + + + elapshed_time + True + False + center + 00:00 + + + False + True + 0 + + + + + seekbar + True + True + center + 1 + False + + + + True + True + 1 + + + + + remain_time + True + False + center + 00:00 + + + False + True + 2 + + + + + volume_button + True + True + True + center + none + False + vertical + audio-volume-muted +audio-volume-high +audio-volume-low +audio-volume-medium + + + + + + True + True + none + + + + + - + True + True + none + + + + + False + True + 3 + + + + + fullscreen_button + True + True + True + fullscreen_image + none + 0 + 0 + + + + False + True + 4 + + + + + False + True + -1 + + + + + title_label + True + False + 5 + center + True + + + False + True + 0 + + + + + True + False + center + 20 + 20 + 10 + + + True + False + center + center + 5 + + + prev_button + True + True + True + center + prev_image + none + 0 + 0 + + + + False + True + 0 + + + + + rewind_button + True + True + True + center + center + rewind_image + none + 0 + 0 + + + + False + True + 1 + + + + + play_pause_button + True + True + True + center + pause_image + none + 0 + 0 + + + + False + True + 2 + + + + + forward_button + True + True + True + center + forward_image + none + 0 + 0 + + + + False + True + 3 + + + + + next_button + True + True + True + center + next_image + none + 0 + 0 + + + + False + True + 4 + + + + + True + True + 0 + + + + + rate_label + 40 + False + center + 0 + 0 + right + + + False + True + end + 1 + + + + + False + True + 2 + + + + From 0a38d3699699a6277f85743ea15edbe2bf093de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Wed, 15 Jul 2015 19:18:11 +0200 Subject: [PATCH 109/412] playback/player: gtk-play: remove unused variables --- playback/player/gtk/gtk-play.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 32973afe7f..ad42da7c56 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -630,7 +630,6 @@ media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) GtkBuilder *dialog_ui; GtkWidget *view; GtkTreeStore *tree; - GtkTreeModel *model; GtkTreeViewColumn *col; GtkCellRenderer *renderer; @@ -1257,8 +1256,7 @@ get_child_position (GtkOverlay * overlay, GtkWidget * widget, static void create_ui (GtkPlay * play) { - GtkWidget *image; - GtkWidget *main_hbox, *main_vbox; + GtkWidget *main_hbox; GstElement *playbin, *gtk_sink; gtk_window_set_default_size (GTK_WINDOW (play), 640, 480); From 64a73ff36b3679eb02f63961c99d105e0f103ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Wed, 15 Jul 2015 19:52:39 +0200 Subject: [PATCH 110/412] playback/player: gtk-play: remove unused callback --- playback/player/gtk/gtk-play.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index ad42da7c56..4c7e369972 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1005,12 +1005,6 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) return menu; } -static void -player_set_rate_cb (GtkSpinButton * button, GtkPlay * play) -{ - gst_player_set_rate (play->player, gtk_spin_button_get_value (button)); -} - static void player_quit_clicked_cb (GtkButton * button, GtkPlay * play) { From a8bb7e0199d7fced6b73220f9719bc7e60eb6202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Wed, 8 Jul 2015 20:56:47 +0200 Subject: [PATCH 111/412] playback/player: gtk-play: handle keys Add a mplayer alike key-binding. --- playback/player/gtk/gtk-play.c | 147 +++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 4c7e369972..1d0491d286 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -207,6 +207,147 @@ gtk_play_set_rate (GtkPlay * play, gdouble step) } } +static inline void +seekbar_add_delta (GtkPlay *play, gint delta_sec) +{ + gdouble value = gtk_range_get_value (GTK_RANGE (play->seekbar)); + gtk_range_set_value (GTK_RANGE (play->seekbar), value + delta_sec); +} + +/* this mapping follow the mplayer key-bindings */ +static gboolean +key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) +{ + GtkPlay *play = (GtkPlay *) widget; + + if (event->state != 0 && + ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_MOD1_MASK) || + (event->state & GDK_MOD3_MASK) || (event->state & GDK_MOD4_MASK))) + return FALSE; + + if (event->type != GDK_KEY_PRESS) + return FALSE; + + switch (event->keyval) { + case GDK_KEY_KP_Right: + case GDK_KEY_Right: { + /* seek forward 10 seconds */ + seekbar_add_delta (play, 10); + break; + } + case GDK_KEY_KP_Left: + case GDK_KEY_Left: { + /* seek backward 10 seconds */ + seekbar_add_delta (play, -10); + break; + } + case GDK_KEY_KP_Up: + case GDK_KEY_Up: { + /* seek forward 1 minute */ + seekbar_add_delta (play, 60); + break; + } + case GDK_KEY_KP_Down: + case GDK_KEY_Down: { + /* seek backward 1 minute */ + seekbar_add_delta (play, -60); + break; + } + case GDK_KEY_KP_Page_Up: + case GDK_KEY_Page_Up: { + /* Seek forward 10 minutes */ + seekbar_add_delta (play, 600); + break; + } + case GDK_KEY_KP_Page_Down: + case GDK_KEY_Page_Down: { + /* Seek backward 10 minutes */ + seekbar_add_delta (play, -600); + break; + } + case GDK_KEY_bracketleft: { + /* Decrease current playback speed by 10% */ + gtk_play_set_rate (play, -0.1); + break; + } + case GDK_KEY_bracketright: { + /* Increase current playback speed by 10% */ + gtk_play_set_rate (play, 0.1); + break; + break; + } + case GDK_KEY_braceleft: { + /* Decrease current playback speed by 10% */ + gtk_play_set_rate (play, -1.0); + break; + } + case GDK_KEY_braceright: { + /* Increase current playback speed by 10% */ + gtk_play_set_rate (play, 1.0); + break; + } + case GDK_KEY_BackSpace: { + /* Reset playback speed to normal */ + gdouble val = gst_player_get_rate (play->player); + gtk_play_set_rate (play, 1.0 - val); + break; + } + case GDK_KEY_less: { + /* Go backward in the playlist */ + if (g_list_previous (play->current_uri)) + gtk_button_clicked (GTK_BUTTON (play->prev_button)); + break; + } + case GDK_KEY_Return: + case GDK_KEY_greater: { + /* Go forward in the playlist */ + if (g_list_next (play->current_uri)) + gtk_button_clicked (GTK_BUTTON (play->next_button)); + break; + } + case GDK_KEY_KP_9: + case GDK_KEY_9: { + /* Increase volume */ + gdouble volume = gst_player_get_volume (play->player); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), + volume * 1.10); + break; + } + case GDK_KEY_KP_0: + case GDK_KEY_0: { + /* Decrease volume */ + gdouble volume = gst_player_get_volume (play->player); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), + volume * 0.9); + break; + } + case GDK_KEY_m: { + /* Mute sound */ + gboolean mute = gst_player_get_mute (play->player); + gst_player_set_mute (play->player, !mute); + break; + } + case GDK_KEY_f: { + /* Toggle fullscreen */ + GtkToggleButton *fs = GTK_TOGGLE_BUTTON (play->fullscreen_button); + gboolean active = !gtk_toggle_button_get_active (fs); + gtk_toggle_button_set_active (fs, active); + break; + } + case GDK_KEY_p: + case GDK_KEY_space: + /* toggle pause/play */ + gtk_button_clicked (GTK_BUTTON (play->play_pause_button)); + break; + case GDK_KEY_q: + case GDK_KEY_Escape: + default: + break; + } + + return FALSE; +} + G_MODULE_EXPORT void rewind_button_clicked_cb (GtkButton * button, GtkPlay * play) { @@ -1257,6 +1398,12 @@ create_ui (GtkPlay * play) g_signal_connect (G_OBJECT (play), "delete-event", G_CALLBACK (delete_event_cb), play); + + gtk_widget_set_events (GTK_WIDGET (play), + GDK_KEY_RELEASE_MASK | GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT (play), "key-press-event", + G_CALLBACK (key_press_event_cb), NULL); + set_title (play, APP_NAME); gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play)); From a18cefb73c2bd2e63d0ba004f8703d1bf811907b Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Wed, 29 Jul 2015 22:52:28 -0500 Subject: [PATCH 112/412] playback/player: gtk-play: fix elapsed and remain time label If elapsed or remain time is greater than 60 mins then add hours in label text. --- playback/player/gtk/gtk-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 1d0491d286..e327606250 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1526,7 +1526,7 @@ update_position_label (GtkLabel * label, guint64 seconds) seconds -= mins * 60; if (hrs) - data = g_strdup_printf ("%02d:%02d", hrs, mins); + data = g_strdup_printf ("%d:%02d:%02ld", hrs, mins, seconds); else data = g_strdup_printf ("%02d:%02ld", mins, seconds); From 7a874938a0a62d41dc91063489df8a521dddebbe Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 30 Jul 2015 07:35:04 -0500 Subject: [PATCH 113/412] playback/player: gtk-play: if title is NULL then use uri basename in toolbar label If stream title is not available in the stream then use file basename in toolbar title label and full uri in window title bar. --- playback/player/gtk/gtk-play.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index e327606250..ed778a383c 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1638,18 +1638,24 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, { const gchar *title; GdkPixbuf *pixbuf; + gchar *basename = NULL; + gchar *filename = NULL; title = gst_player_media_info_get_title (media_info); - if (!title) - title = g_path_get_basename (gst_player_media_info_get_uri (media_info)); + + if (!title) { + filename = g_filename_from_uri( + gst_player_media_info_get_uri (media_info), NULL, NULL); + basename = g_path_get_basename (filename); + } + + gtk_label_set_label (play->title_label, title ? title : basename); + set_title (play, title ? title : filename); + g_free(basename); + g_free(filename); pixbuf = gtk_play_get_cover_image (media_info); - if (title) { - gtk_label_set_label (play->title_label, title); - set_title (play, title); - } - if (pixbuf) { gtk_window_set_icon (GTK_WINDOW (play), pixbuf); g_object_unref (pixbuf); From f68bac6318085b051221bc1db1084ab8fce06d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Jul 2015 15:54:26 +0300 Subject: [PATCH 114/412] playback/player: gtk-play: Fix indention --- playback/player/gtk/gtk-play.c | 85 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index ed778a383c..b1ad7ac373 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -74,10 +74,10 @@ typedef struct GtkWidget *toolbar; GtkWidget *toolbar_overlay; GtkWidget *media_info_dialog; - GtkLabel *title_label; - GtkLabel *elapshed_label; - GtkLabel *remain_label; - GtkLabel *rate_label; + GtkLabel *title_label; + GtkLabel *elapshed_label; + GtkLabel *remain_label; + GtkLabel *rate_label; GdkCursor *default_cursor; gboolean playing; gboolean loop; @@ -208,7 +208,7 @@ gtk_play_set_rate (GtkPlay * play, gdouble step) } static inline void -seekbar_add_delta (GtkPlay *play, gint delta_sec) +seekbar_add_delta (GtkPlay * play, gint delta_sec) { gdouble value = gtk_range_get_value (GTK_RANGE (play->seekbar)); gtk_range_set_value (GTK_RANGE (play->seekbar), value + delta_sec); @@ -222,7 +222,7 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) if (event->state != 0 && ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_MOD1_MASK) || - (event->state & GDK_MOD3_MASK) || (event->state & GDK_MOD4_MASK))) + (event->state & GDK_MOD3_MASK) || (event->state & GDK_MOD4_MASK))) return FALSE; if (event->type != GDK_KEY_PRESS) @@ -230,83 +230,83 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) switch (event->keyval) { case GDK_KEY_KP_Right: - case GDK_KEY_Right: { + case GDK_KEY_Right:{ /* seek forward 10 seconds */ seekbar_add_delta (play, 10); break; } case GDK_KEY_KP_Left: - case GDK_KEY_Left: { + case GDK_KEY_Left:{ /* seek backward 10 seconds */ seekbar_add_delta (play, -10); break; } case GDK_KEY_KP_Up: - case GDK_KEY_Up: { + case GDK_KEY_Up:{ /* seek forward 1 minute */ seekbar_add_delta (play, 60); break; } case GDK_KEY_KP_Down: - case GDK_KEY_Down: { + case GDK_KEY_Down:{ /* seek backward 1 minute */ seekbar_add_delta (play, -60); break; } case GDK_KEY_KP_Page_Up: - case GDK_KEY_Page_Up: { + case GDK_KEY_Page_Up:{ /* Seek forward 10 minutes */ seekbar_add_delta (play, 600); break; } case GDK_KEY_KP_Page_Down: - case GDK_KEY_Page_Down: { + case GDK_KEY_Page_Down:{ /* Seek backward 10 minutes */ seekbar_add_delta (play, -600); break; } - case GDK_KEY_bracketleft: { + case GDK_KEY_bracketleft:{ /* Decrease current playback speed by 10% */ gtk_play_set_rate (play, -0.1); break; } - case GDK_KEY_bracketright: { + case GDK_KEY_bracketright:{ /* Increase current playback speed by 10% */ gtk_play_set_rate (play, 0.1); break; break; } - case GDK_KEY_braceleft: { + case GDK_KEY_braceleft:{ /* Decrease current playback speed by 10% */ gtk_play_set_rate (play, -1.0); break; } - case GDK_KEY_braceright: { + case GDK_KEY_braceright:{ /* Increase current playback speed by 10% */ gtk_play_set_rate (play, 1.0); break; } - case GDK_KEY_BackSpace: { + case GDK_KEY_BackSpace:{ /* Reset playback speed to normal */ gdouble val = gst_player_get_rate (play->player); gtk_play_set_rate (play, 1.0 - val); break; } - case GDK_KEY_less: { + case GDK_KEY_less:{ /* Go backward in the playlist */ if (g_list_previous (play->current_uri)) gtk_button_clicked (GTK_BUTTON (play->prev_button)); break; } case GDK_KEY_Return: - case GDK_KEY_greater: { + case GDK_KEY_greater:{ /* Go forward in the playlist */ if (g_list_next (play->current_uri)) gtk_button_clicked (GTK_BUTTON (play->next_button)); break; } case GDK_KEY_KP_9: - case GDK_KEY_9: { + case GDK_KEY_9:{ /* Increase volume */ gdouble volume = gst_player_get_volume (play->player); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), @@ -314,20 +314,20 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) break; } case GDK_KEY_KP_0: - case GDK_KEY_0: { + case GDK_KEY_0:{ /* Decrease volume */ gdouble volume = gst_player_get_volume (play->player); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), volume * 0.9); break; } - case GDK_KEY_m: { + case GDK_KEY_m:{ /* Mute sound */ gboolean mute = gst_player_get_mute (play->player); gst_player_set_mute (play->player, !mute); break; } - case GDK_KEY_f: { + case GDK_KEY_f:{ /* Toggle fullscreen */ GtkToggleButton *fs = GTK_TOGGLE_BUTTON (play->fullscreen_button); gboolean active = !gtk_toggle_button_get_active (fs); @@ -779,7 +779,7 @@ media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) return; play->media_info_dialog = - (GtkWidget *) gtk_builder_get_object (dialog_ui, "media_info_dialog"); + (GtkWidget *) gtk_builder_get_object (dialog_ui, "media_info_dialog"); gtk_window_set_transient_for (GTK_WINDOW (play->media_info_dialog), GTK_WINDOW (play)); @@ -796,7 +796,7 @@ media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) g_signal_connect (view, "realize", G_CALLBACK (gtk_tree_view_expand_all), NULL); - + gtk_widget_set_size_request (play->media_info_dialog, 550, 450); gtk_widget_show_all (play->media_info_dialog); @@ -887,7 +887,8 @@ seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) } G_MODULE_EXPORT void -volume_button_value_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) +volume_button_value_changed_cb (GtkScaleButton * button, gdouble value, + GtkPlay * play) { gst_player_set_volume (play->player, value); } @@ -1282,7 +1283,8 @@ mouse_button_pressed_cb (GtkWidget * unused, GdkEventButton * event, } static gboolean -video_area_leave_notify_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +video_area_leave_notify_cb (GtkWidget * widget, GdkEvent * event, + GtkPlay * play) { start_toolbar_hide_timer (play); @@ -1290,7 +1292,8 @@ video_area_leave_notify_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play } static gboolean -video_area_toolbar_show_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +video_area_toolbar_show_cb (GtkWidget * widget, GdkEvent * event, + GtkPlay * play) { toolbar_show (play); @@ -1300,7 +1303,8 @@ video_area_toolbar_show_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play } static gboolean -overlay_leave_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +overlay_leave_notify_event_cb (GtkWidget * widget, GdkEvent * event, + GtkPlay * play) { start_toolbar_hide_timer (play); @@ -1308,7 +1312,8 @@ overlay_leave_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * p } static gboolean -overlay_enter_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) +overlay_enter_notify_event_cb (GtkWidget * widget, GdkEvent * event, + GtkPlay * play) { toolbar_show (play); @@ -1316,7 +1321,7 @@ overlay_enter_notify_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * p } static void -apply_css (GtkWidget *widget, GtkStyleProvider *provider) +apply_css (GtkWidget * widget, GtkStyleProvider * provider) { gtk_style_context_add_provider (gtk_widget_get_style_context (widget), provider, G_MAXUINT); @@ -1329,7 +1334,7 @@ apply_css (GtkWidget *widget, GtkStyleProvider *provider) static void gtk_widget_apply_css (GtkWidget * widget, const gchar * filename) { - GBytes *bytes; + GBytes *bytes; gsize data_size; const guint8 *data; GError *err = NULL; @@ -1346,7 +1351,7 @@ gtk_widget_apply_css (GtkWidget * widget, const gchar * filename) } data = g_bytes_get_data (bytes, &data_size); gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), - (gchar *)data, data_size, NULL); + (gchar *) data, data_size, NULL); g_bytes_unref (bytes); apply_css (widget, provider); @@ -1360,7 +1365,7 @@ get_child_position (GtkOverlay * overlay, GtkWidget * widget, GtkWidget *child; GtkAllocation main_alloc; gint x, y; - GtkWidget * relative = play->video_area; + GtkWidget *relative = play->video_area; child = gtk_bin_get_child (GTK_BIN (overlay)); gtk_widget_translate_coordinates (relative, child, 0, 0, &x, &y); @@ -1500,7 +1505,7 @@ create_ui (GtkPlay * play) /* check if we need to enable fullscreen */ if (play->fullscreen) gtk_toggle_button_set_active - (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); + (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); /* enable visualization (by default laybin uses goom) */ /* if visualization is enabled then use the first element */ @@ -1632,6 +1637,7 @@ gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) return pixbuf; } + static void media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, GtkPlay * play) @@ -1644,15 +1650,16 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, title = gst_player_media_info_get_title (media_info); if (!title) { - filename = g_filename_from_uri( - gst_player_media_info_get_uri (media_info), NULL, NULL); + filename = + g_filename_from_uri (gst_player_media_info_get_uri (media_info), NULL, + NULL); basename = g_path_get_basename (filename); } gtk_label_set_label (play->title_label, title ? title : basename); set_title (play, title ? title : filename); - g_free(basename); - g_free(filename); + g_free (basename); + g_free (filename); pixbuf = gtk_play_get_cover_image (media_info); From 6ed6b5892925c2b1b7aca5068964b8f8607b206e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 12 Aug 2015 16:00:15 +0200 Subject: [PATCH 115/412] playback/player: player: Abstract the signal emission dispatching to a new interface This allows to implement signal dispatching to other event loop systems than GLib without having direct GLib support in the base GstPlayer class. A implementation for the GLib main context is provided and used in the applications and tests. --- playback/player/gst-play/gst-play.c | 10 +++++----- playback/player/gtk/gtk-play.c | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index f22e3f23d3..368b814e8a 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -351,9 +351,10 @@ play_new (gchar ** uris, gdouble initial_volume) play->num_uris = g_strv_length (uris); play->cur_idx = -1; - play->player = gst_player_new (); + play->player = + gst_player_new_full (gst_player_g_main_context_signal_dispatcher_new + (NULL)); - g_object_set (play->player, "dispatch-to-main-context", TRUE, NULL); g_signal_connect (play->player, "position-updated", G_CALLBACK (position_updated_cb), play); g_signal_connect (play->player, "state-changed", @@ -447,9 +448,8 @@ play_next (GstPlay * play) if (play->repeat) { g_print ("Looping playlist \n"); play->cur_idx = -1; - } - else - return FALSE; + } else + return FALSE; } play_uri (play, play->uris[++play->cur_idx]); diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index b1ad7ac373..c601f32a81 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1730,7 +1730,9 @@ gtk_play_constructor (GType type, guint n_construct_params, (GtkPlay *) G_OBJECT_CLASS (gtk_play_parent_class)->constructor (type, n_construct_params, construct_params); - self->player = gst_player_new (); + self->player = + gst_player_new_full (gst_player_g_main_context_signal_dispatcher_new + (NULL)); self->playing = TRUE; if (self->inhibit_cookie) @@ -1740,8 +1742,6 @@ gtk_play_constructor (GType type, guint n_construct_params, gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (self), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); - g_object_set (self->player, "dispatch-to-main-context", TRUE, NULL); - create_ui (self); g_signal_connect (self->player, "position-updated", From a8f8d1b03250d22da22ec0ffa7b14fbe096e794f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 14 Aug 2015 17:13:39 +0200 Subject: [PATCH 116/412] playback/player: player: Refactor video rendering API There's a GstPlayerVideoRenderer interface now, which defines how video rendering happens in GstPlayer. Included is an implementation for the GstVideoOverlay interface, and inside the GTK example application one for gtksink/gtkglsink. --- playback/player/gst-play/gst-play.c | 2 +- playback/player/gtk/Makefile.am | 4 +- playback/player/gtk/gtk-play.c | 44 +++--- playback/player/gtk/gtk-video-renderer.c | 178 +++++++++++++++++++++++ playback/player/gtk/gtk-video-renderer.h | 49 +++++++ 5 files changed, 249 insertions(+), 28 deletions(-) create mode 100644 playback/player/gtk/gtk-video-renderer.c create mode 100644 playback/player/gtk/gtk-video-renderer.h diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 368b814e8a..84176690ed 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -352,7 +352,7 @@ play_new (gchar ** uris, gdouble initial_volume) play->cur_idx = -1; play->player = - gst_player_new_full (gst_player_g_main_context_signal_dispatcher_new + gst_player_new_full (NULL, gst_player_g_main_context_signal_dispatcher_new (NULL)); g_signal_connect (play->player, "position-updated", diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am index f7e6d0f58b..8e2331a983 100644 --- a/playback/player/gtk/Makefile.am +++ b/playback/player/gtk/Makefile.am @@ -26,11 +26,11 @@ gtk-play-resources.h: resources/gresources.xml \ BUILT_SOURCES: gtk-play-resources.c gtk-play-resources.h -gtk_play_SOURCES = gtk-play.c gtk-play-resources.c +gtk_play_SOURCES = gtk-play.c gtk-play-resources.c gtk-video-renderer.c LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) -noinst_HEADERS = gtk-play-resources.h +noinst_HEADERS = gtk-play-resources.h gtk-video-renderer.h diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index c601f32a81..906a672bab 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -39,6 +39,7 @@ #include #include +#include "gtk-video-renderer.h" #define APP_NAME "gtk-play" @@ -58,6 +59,7 @@ typedef struct GtkApplicationWindow parent; GstPlayer *player; + GstPlayerVideoRenderer *renderer; gchar *uri; GList *uris; @@ -182,7 +184,8 @@ video_area_realize_cb (GtkWidget * widget, GtkPlay * play) #elif defined (GDK_WINDOWING_X11) window_handle = GDK_WINDOW_XID (window); #endif - g_object_set (play->player, "window-handle", (gpointer) window_handle, NULL); + g_object_set (play->renderer, "window-handle", (gpointer) window_handle, + NULL); } static void @@ -1413,24 +1416,14 @@ create_ui (GtkPlay * play) gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play)); - if ((gtk_sink = gst_element_factory_make ("gtkglsink", NULL))) { - GstElement *video_sink; - - g_object_get (gtk_sink, "widget", &play->video_area, NULL); - - video_sink = gst_element_factory_make ("glsinkbin", NULL); - g_object_set (video_sink, "sink", gtk_sink, NULL); - - playbin = gst_player_get_pipeline (play->player); - g_object_set (playbin, "video-sink", video_sink, NULL); - gst_object_unref (playbin); - } else if ((gtk_sink = gst_element_factory_make ("gtksink", NULL))) { - g_object_get (gtk_sink, "widget", &play->video_area, NULL); - - playbin = gst_player_get_pipeline (play->player); - g_object_set (playbin, "video-sink", gtk_sink, NULL); - gst_object_unref (playbin); + play->renderer = gst_player_gtk_video_renderer_new (); + if (play->renderer) { + play->video_area = + gst_player_gtk_video_renderer_get_widget (GST_PLAYER_GTK_VIDEO_RENDERER + (play->renderer)); } else { + play->renderer = gst_player_video_overlay_video_renderer_new (NULL); + play->video_area = gtk_drawing_area_new (); g_signal_connect (play->video_area, "realize", G_CALLBACK (video_area_realize_cb), play); @@ -1506,10 +1499,6 @@ create_ui (GtkPlay * play) if (play->fullscreen) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (play->fullscreen_button), TRUE); - - /* enable visualization (by default laybin uses goom) */ - /* if visualization is enabled then use the first element */ - gst_player_set_visualization_enabled (play->player, TRUE); } static void @@ -1730,9 +1719,6 @@ gtk_play_constructor (GType type, guint n_construct_params, (GtkPlay *) G_OBJECT_CLASS (gtk_play_parent_class)->constructor (type, n_construct_params, construct_params); - self->player = - gst_player_new_full (gst_player_g_main_context_signal_dispatcher_new - (NULL)); self->playing = TRUE; if (self->inhibit_cookie) @@ -1744,6 +1730,10 @@ gtk_play_constructor (GType type, guint n_construct_params, create_ui (self); + self->player = + gst_player_new_full (self->renderer, + gst_player_g_main_context_signal_dispatcher_new (NULL)); + g_signal_connect (self->player, "position-updated", G_CALLBACK (position_updated_cb), self); g_signal_connect (self->player, "duration-changed", @@ -1754,6 +1744,10 @@ gtk_play_constructor (GType type, guint n_construct_params, g_signal_connect (self->player, "volume-changed", G_CALLBACK (player_volume_changed_cb), self); + /* enable visualization (by default playbin uses goom) */ + /* if visualization is enabled then use the first element */ + gst_player_set_visualization_enabled (self->player, TRUE); + g_signal_connect (G_OBJECT (self), "show", G_CALLBACK (show_cb), NULL); return G_OBJECT (self); diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c new file mode 100644 index 0000000000..6ff568efc6 --- /dev/null +++ b/playback/player/gtk/gtk-video-renderer.c @@ -0,0 +1,178 @@ +/* GStreamer + * + * Copyright (C) 2015 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gtk-video-renderer.h" + +struct _GstPlayerGtkVideoRenderer +{ + GObject parent; + + GstElement *sink; + GtkWidget *widget; +}; + +struct _GstPlayerGtkVideoRendererClass +{ + GObjectClass parent_class; +}; + +static void + gst_player_gtk_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface); + +enum +{ + GTK_VIDEO_RENDERER_PROP_0, + GTK_VIDEO_RENDERER_PROP_WIDGET, + GTK_VIDEO_RENDERER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstPlayerGtkVideoRenderer, + gst_player_gtk_video_renderer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER, + gst_player_gtk_video_renderer_interface_init)); + +static GParamSpec + * gtk_video_renderer_param_specs[GTK_VIDEO_RENDERER_PROP_LAST] = { NULL, }; + +static void +gst_player_gtk_video_renderer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (object); + + switch (prop_id) { + case GTK_VIDEO_RENDERER_PROP_WIDGET: + g_value_set_object (value, self->widget); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_gtk_video_renderer_finalize (GObject * object) +{ + GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (object); + + if (self->sink) + gst_object_unref (self->sink); + if (self->widget) + g_object_unref (self->widget); + + G_OBJECT_CLASS + (gst_player_gtk_video_renderer_parent_class)->finalize (object); +} + +static void + gst_player_gtk_video_renderer_class_init + (GstPlayerGtkVideoRendererClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gst_player_gtk_video_renderer_get_property; + gobject_class->finalize = gst_player_gtk_video_renderer_finalize; + + gtk_video_renderer_param_specs + [GTK_VIDEO_RENDERER_PROP_WIDGET] = + g_param_spec_object ("widget", "Widget", + "Widget to render the video into", GTK_TYPE_WIDGET, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + GTK_VIDEO_RENDERER_PROP_LAST, gtk_video_renderer_param_specs); +} + +static void +gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) +{ + GstElement *gtk_sink = gst_element_factory_make ("gtkglsink", NULL); + + if (gtk_sink) { + GstElement *sink = sink = gst_element_factory_make ("glsinkbin", NULL); + g_object_set (sink, "sink", gtk_sink, NULL); + + self->sink = sink; + } else { + gtk_sink = gst_element_factory_make ("gtksink", NULL); + + self->sink = gtk_sink; + } + + g_assert (self->sink != NULL); + + g_object_get (gtk_sink, "widget", &self->widget, NULL); +} + +static GstElement *gst_player_gtk_video_renderer_create_video_sink + (GstPlayerVideoRenderer * iface, GstPlayer * player) +{ + GstElement *gtk; + GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (iface); + + return gst_object_ref (self->sink); +} + +static void + gst_player_gtk_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface) +{ + iface->create_video_sink = gst_player_gtk_video_renderer_create_video_sink; +} + +/** + * gst_player_gtk_video_renderer_new: + * + * Returns: (transfer full): + */ +GstPlayerVideoRenderer * +gst_player_gtk_video_renderer_new (void) +{ + GstElementFactory *factory; + + factory = gst_element_factory_find ("gtkglsink"); + if (!factory) + factory = gst_element_factory_find ("gtksink"); + if (!factory) + return NULL; + + gst_object_unref (factory); + + return g_object_new (GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, NULL); +} + +/** + * gst_player_gtk_video_renderer_get_widget: + * @self: #GstPlayerVideoRenderer instance + * + * Returns: (transfer full): The GtkWidget + */ +GtkWidget *gst_player_gtk_video_renderer_get_widget + (GstPlayerGtkVideoRenderer * self) +{ + GtkWidget *widget; + + g_return_if_fail (GST_IS_PLAYER_GTK_VIDEO_RENDERER (self)); + + g_object_get (self, "widget", &widget, NULL); + + return widget; +} diff --git a/playback/player/gtk/gtk-video-renderer.h b/playback/player/gtk/gtk-video-renderer.h new file mode 100644 index 0000000000..482b609eb1 --- /dev/null +++ b/playback/player/gtk/gtk-video-renderer.h @@ -0,0 +1,49 @@ +/* GStreamer + * + * Copyright (C) 2015 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GTK_VIDEO_RENDERER_H__ +#define __GTK_VIDEO_RENDERER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerGtkVideoRenderer + GstPlayerGtkVideoRenderer; +typedef struct _GstPlayerGtkVideoRendererClass + GstPlayerGtkVideoRendererClass; + +#define GST_TYPE_PLAYER_GTK_VIDEO_RENDERER (gst_player_gtk_video_renderer_get_type ()) +#define GST_IS_PLAYER_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER)) +#define GST_IS_PLAYER_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER)) +#define GST_PLAYER_GTK_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRendererClass)) +#define GST_PLAYER_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRenderer)) +#define GST_PLAYER_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRendererClass)) +#define GST_PLAYER_GTK_VIDEO_RENDERER_CAST(obj) ((GstPlayerGtkVideoRenderer*)(obj)) + +GType gst_player_gtk_video_renderer_get_type (void); + +GstPlayerVideoRenderer * gst_player_gtk_video_renderer_new (void); +GtkWidget * gst_player_gtk_video_renderer_get_widget (GstPlayerGtkVideoRenderer * self); + +G_END_DECLS + +#endif /* __GTK_VIDEO_RENDERER_H__ */ From 2da236d871b467067176fe1b9e3c2eedef9e6e70 Mon Sep 17 00:00:00 2001 From: trungdoan Date: Tue, 18 Aug 2015 16:34:19 +0700 Subject: [PATCH 117/412] playback/player: iOS: Fix build issue Fix build issue on iOS by adding additional files in lib/gst/player to the iOS project. --- playback/player/ios/GstPlay.xcodeproj/project.pbxproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj index ee94d2d707..9a3e45cf45 100644 --- a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ AD2B8861198D65780070367B /* LibraryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B885E198D65780070367B /* LibraryViewController.m */; }; AD2B8862198D65780070367B /* VideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8860198D65780070367B /* VideoViewController.m */; }; AD2B886C198D69ED0070367B /* gstplayer.c in Sources */ = {isa = PBXBuildFile; fileRef = AD2B886A198D69ED0070367B /* gstplayer.c */; }; + E95DEC9B1B8332F100CC3512 /* gstplayer-media-info.c in Sources */ = {isa = PBXBuildFile; fileRef = E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -54,6 +55,10 @@ AD2B8860198D65780070367B /* VideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoViewController.m; sourceTree = ""; }; AD2B886A198D69ED0070367B /* gstplayer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gstplayer.c; sourceTree = ""; }; AD2B886B198D69ED0070367B /* gstplayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gstplayer.h; sourceTree = ""; }; + E95DEC971B8332F100CC3512 /* gstplayer-media-info-private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "gstplayer-media-info-private.h"; sourceTree = ""; }; + E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "gstplayer-media-info.c"; sourceTree = ""; }; + E95DEC991B8332F100CC3512 /* gstplayer-media-info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "gstplayer-media-info.h"; sourceTree = ""; }; + E95DEC9A1B8332F100CC3512 /* player.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = player.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -147,6 +152,10 @@ AD2B8869198D69ED0070367B /* player */ = { isa = PBXGroup; children = ( + E95DEC971B8332F100CC3512 /* gstplayer-media-info-private.h */, + E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */, + E95DEC991B8332F100CC3512 /* gstplayer-media-info.h */, + E95DEC9A1B8332F100CC3512 /* player.h */, AD2B886A198D69ED0070367B /* gstplayer.c */, AD2B886B198D69ED0070367B /* gstplayer.h */, ); @@ -220,6 +229,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E95DEC9B1B8332F100CC3512 /* gstplayer-media-info.c in Sources */, AD2B8861198D65780070367B /* LibraryViewController.m in Sources */, AD2B8831198D631B0070367B /* AppDelegate.m in Sources */, AD2B8862198D65780070367B /* VideoViewController.m in Sources */, From 80e4c1abff1847378a99fb295a4585aa597de921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 18 Aug 2015 13:17:57 +0300 Subject: [PATCH 118/412] playback/player: ios: Fix video rendering after GstPlayerVideoRenderer refactoring --- playback/player/ios/GstPlay/VideoViewController.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index dcf06b24b3..0516764dbe 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -63,8 +63,7 @@ media_width = 320; media_height = 240; - player = gst_player_new(); - g_object_set (player, "window-handle", video_view, NULL); + player = gst_player_new_full (gst_player_video_overlay_video_renderer_new (video_view), NULL); g_object_set (player, "uri", [uri UTF8String], NULL); gst_debug_set_threshold_for_name("gst-player", GST_LEVEL_TRACE); From c58123346442f503dee0ca61d5542e138ea7c80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 18 Aug 2015 13:20:15 +0300 Subject: [PATCH 119/412] playback/player: android: Fix video rendering after GstPlayerVideoRenderer refactoring --- playback/player/android/app/src/main/jni/player.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/playback/player/android/app/src/main/jni/player.c b/playback/player/android/app/src/main/jni/player.c index 882c230e4b..133d97090f 100644 --- a/playback/player/android/app/src/main/jni/player.c +++ b/playback/player/android/app/src/main/jni/player.c @@ -37,6 +37,7 @@ typedef struct _Player { jobject java_player; GstPlayer *player; + GstPlayerVideoRenderer *renderer; ANativeWindow *native_window; } Player; @@ -197,7 +198,8 @@ native_new (JNIEnv * env, jobject thiz) { Player *player = g_new0 (Player, 1); - player->player = gst_player_new (); + player->renderer = gst_player_video_overlay_video_renderer_new (NULL); + player->player = gst_player_new_full (player->renderer, NULL); SET_CUSTOM_DATA (env, thiz, native_player_field_id, player); player->java_player = (*env)->NewGlobalRef (env, thiz); @@ -402,8 +404,9 @@ native_set_surface (JNIEnv * env, jobject thiz, jobject surface) } player->native_window = new_native_window; - g_object_set (player->player, "window-handle", (gpointer) new_native_window, - NULL); + gst_player_video_overlay_video_renderer_set_window_handle + (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer), + (gpointer) new_native_window); } static void From e2581982a6629066c53985ad7f42b45a044de640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 20 Aug 2015 10:50:30 +0300 Subject: [PATCH 120/412] playback/player: gst-play: Fix leak of GError and GOptionContext on parsing errors --- playback/player/gst-play/gst-play.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 84176690ed..e00a7fc4e8 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -674,6 +674,8 @@ main (int argc, char **argv) g_option_context_add_group (ctx, gst_init_get_option_group ()); if (!g_option_context_parse (ctx, &argc, &argv, &err)) { g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + g_clear_error (&err); + g_option_context_free (ctx); return 1; } g_option_context_free (ctx); From 50099a3aa19c1bb26455e1d9b4d9c7e879990ea2 Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Wed, 26 Aug 2015 18:57:13 +0900 Subject: [PATCH 121/412] playback/player: gst-play: Check if the stream list is empty in print_all_stream_info before using it --- playback/player/gst-play/gst-play.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index e00a7fc4e8..6fa304e9e6 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -195,9 +195,12 @@ static void print_all_stream_info (GstPlayerMediaInfo * media_info) { guint count = 0; - GList *l, *list; + GList *list, *l; list = gst_player_media_info_get_stream_list (media_info); + if (!list) + return; + g_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); g_print ("Duration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); From 371f4ec378fd418bec95bc2d165508cd2490aa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 27 Aug 2015 10:53:43 +0300 Subject: [PATCH 122/412] playback/player: gst-play: Remove useless variable initializations They are set to something in the next line anyway. --- playback/player/gst-play/gst-play.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 6fa304e9e6..ed7e1dd6f8 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -236,7 +236,7 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) static void print_all_video_stream (GstPlayerMediaInfo * media_info) { - GList *list = NULL, *l; + GList *list, *l; list = gst_player_get_video_streams (media_info); if (!list) @@ -255,7 +255,7 @@ print_all_video_stream (GstPlayerMediaInfo * media_info) static void print_all_subtitle_stream (GstPlayerMediaInfo * media_info) { - GList *list = NULL, *l; + GList *list, *l; list = gst_player_get_subtitle_streams (media_info); if (!list) @@ -274,7 +274,7 @@ print_all_subtitle_stream (GstPlayerMediaInfo * media_info) static void print_all_audio_stream (GstPlayerMediaInfo * media_info) { - GList *list = NULL, *l; + GList *list, *l; list = gst_player_get_audio_streams (media_info); if (!list) From 6e9e9228bd422d5656be7c2a22cdaba1863621ba Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Wed, 2 Sep 2015 00:53:46 +0800 Subject: [PATCH 123/412] playback/player: gtk-play: Remove unused variable --- playback/player/gtk/gtk-video-renderer.c | 1 - 1 file changed, 1 deletion(-) diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index 6ff568efc6..01921b0fce 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -125,7 +125,6 @@ gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) static GstElement *gst_player_gtk_video_renderer_create_video_sink (GstPlayerVideoRenderer * iface, GstPlayer * player) { - GstElement *gtk; GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (iface); return gst_object_ref (self->sink); From 0caa80c26fbaf3569043600905b1fdfbbf8b956d Mon Sep 17 00:00:00 2001 From: Justin Kim Date: Thu, 3 Sep 2015 18:29:04 +0900 Subject: [PATCH 124/412] playback/player: ios: remove implicit conversion of UIView to c pointer --- playback/player/ios/GstPlay/VideoViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index 0516764dbe..eda419c0e2 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -63,7 +63,7 @@ media_width = 320; media_height = 240; - player = gst_player_new_full (gst_player_video_overlay_video_renderer_new (video_view), NULL); + player = gst_player_new_full (gst_player_video_overlay_video_renderer_new ((__bridge gpointer)(video_view)), NULL); g_object_set (player, "uri", [uri UTF8String], NULL); gst_debug_set_threshold_for_name("gst-player", GST_LEVEL_TRACE); From b5c10865aa719333ce8c5b698ef9ceee3aeb68e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 4 Sep 2015 18:11:25 +0300 Subject: [PATCH 125/412] playback/player: gtk-play: Add compiler warning flags to the build and fix the warnings --- playback/player/gtk/Makefile.am | 2 +- playback/player/gtk/gtk-play.c | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am index 8e2331a983..97ce95dce3 100644 --- a/playback/player/gtk/Makefile.am +++ b/playback/player/gtk/Makefile.am @@ -31,6 +31,6 @@ gtk_play_SOURCES = gtk-play.c gtk-play-resources.c gtk-video-renderer.c LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) -AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) +AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) $(WARNING_CFLAGS) noinst_HEADERS = gtk-play-resources.h gtk-video-renderer.h diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 906a672bab..aa4522929a 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -52,6 +52,7 @@ typedef GtkApplication GtkPlayApp; typedef GtkApplicationClass GtkPlayAppClass; +GType gtk_play_app_get_type (void); G_DEFINE_TYPE (GtkPlayApp, gtk_play_app, GTK_TYPE_APPLICATION); typedef struct @@ -91,8 +92,20 @@ typedef struct typedef GtkApplicationWindowClass GtkPlayClass; +GType gtk_play_get_type (void); G_DEFINE_TYPE (GtkPlay, gtk_play, GTK_TYPE_APPLICATION_WINDOW); +void rewind_button_clicked_cb (GtkButton * button, GtkPlay * play); +void forward_button_clicked_cb (GtkButton * button, GtkPlay * play); +void play_pause_button_clicked_cb (GtkButton * button, GtkPlay * play); +void prev_button_clicked_cb (GtkButton * button, GtkPlay * play); +void next_button_clicked_cb (GtkButton * button, GtkPlay * play); +void media_info_dialog_button_clicked_cb (GtkButton * button, GtkPlay * play); +void fullscreen_button_toggled_cb (GtkToggleButton * widget, GtkPlay * play); +void seekbar_value_changed_cb (GtkRange * range, GtkPlay * play); +void volume_button_value_changed_cb (GtkScaleButton * button, gdouble value, + GtkPlay * play); + enum { PROP_0, @@ -1071,7 +1084,7 @@ create_visualization_menu (GtkPlay * play) sep = gtk_separator_menu_item_new (); item = gtk_radio_menu_item_new_with_label (group, "Disable"); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); - g_object_set_data (G_OBJECT (item), "name", "disable"); + g_object_set_data (G_OBJECT (item), "name", (gpointer) "disable"); if (cur_vis == NULL) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); g_signal_connect (G_OBJECT (item), "toggled", @@ -1400,7 +1413,6 @@ static void create_ui (GtkPlay * play) { GtkWidget *main_hbox; - GstElement *playbin, *gtk_sink; gtk_window_set_default_size (GTK_WINDOW (play), 640, 480); @@ -1488,8 +1500,7 @@ create_ui (GtkPlay * play) /* apply css on widgets */ gtk_widget_apply_css (play->toolbar, "/css/toolbar.css"); - if (!gtk_sink) - gtk_widget_realize (play->video_area); + gtk_widget_realize (play->video_area); gtk_widget_hide (play->video_area); /* start toolbar autohide timer */ @@ -1867,7 +1878,7 @@ gtk_play_app_class_init (GtkPlayAppClass * klass) application_class->command_line = gtk_play_app_command_line; } -GtkPlayApp * +static GtkPlayApp * gtk_play_app_new (void) { GtkPlayApp *self; From c454b2914e23fff31813a7d3163731d6c51d024b Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Tue, 15 Sep 2015 21:54:38 +0900 Subject: [PATCH 126/412] playback/player: gst-play: Check if the stream list is empty in print_all_stream_info Add a check condition if the stream list is empty before using it. --- playback/player/gst-play/gst-play.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index ed7e1dd6f8..d0924bd219 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -197,10 +197,6 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) guint count = 0; GList *list, *l; - list = gst_player_media_info_get_stream_list (media_info); - if (!list) - return; - g_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); g_print ("Duration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); @@ -210,6 +206,12 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) print_one_tag, NULL); else g_print (" (nil) \n"); + + list = gst_player_media_info_get_stream_list (media_info); + if (!list) + return; + + g_print ("All Stream information\n"); for (l = list; l != NULL; l = l->next) { GstTagList *tags = NULL; GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; From 4dcc43b93562bb7ee7409060d9da972ddd1c598d Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Tue, 15 Sep 2015 22:00:03 +0900 Subject: [PATCH 127/412] playback/player: gtk-play: Modify switch statement in stream_info_get_string In stream_info_get_string function, buffer variable is used all switch case repeatedly. Also, return used in switch statement. So, I have modified that cases. --- playback/player/gtk/gtk-play.c | 44 +++++++++++++--------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index aa4522929a..062fdd20cd 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -623,108 +623,98 @@ audio_channels_string (gint num) static gchar * stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) { + gchar *buffer = NULL; + switch (type) { case AUDIO_INFO_RATE: { - gchar *buffer; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; buffer = g_strdup_printf ("%s%d", label ? "Sample rate : " : "", gst_player_audio_info_get_sample_rate (audio)); - return buffer; + break; } case AUDIO_INFO_LANGUAGE: { - gchar *buffer; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; - if (!gst_player_audio_info_get_language (audio)) - return NULL; - buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", - gst_player_audio_info_get_language (audio)); - return buffer; + const gchar* lang = gst_player_audio_info_get_language (audio); + if (lang) + buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", lang); + break; } case AUDIO_INFO_CHANNELS: { - gchar *buffer; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Channels : " : "", audio_channels_string (gst_player_audio_info_get_channels (audio))); - return buffer; + break; } case SUBTITLE_INFO_CODEC: case VIDEO_INFO_CODEC: case AUDIO_INFO_CODEC: { - gchar *buffer; buffer = g_strdup_printf ("%s%s", label ? "Codec : " : "", gst_player_stream_info_get_codec (stream)); - return buffer; + break; } case AUDIO_INFO_MAX_BITRATE: { - gchar *buffer = NULL; GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; gint bitrate = gst_player_audio_info_get_max_bitrate (audio); if (bitrate > 0) buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", bitrate); - return buffer; + break; } case VIDEO_INFO_MAX_BITRATE: { - gchar *buffer = NULL; GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gint bitrate = gst_player_video_info_get_max_bitrate (video); if (bitrate > 0) buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", bitrate); - return buffer; + break; } case VIDEO_INFO_PAR: { guint par_d, par_n; - gchar *buffer; GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); buffer = g_strdup_printf ("%s%u:%u", label ? "pixel-aspect-ratio : " : "", par_n, par_d); - return buffer; + break; } case VIDEO_INFO_FPS: { gint fps_d, fps_n; - gchar *buffer; GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; gst_player_video_info_get_framerate (video, &fps_n, &fps_d); buffer = g_strdup_printf ("%s%.2f", label ? "Framerate : " : "", (gdouble) fps_n / fps_d); - return buffer; + break; } case VIDEO_INFO_RESOLUTION: { - gchar *buffer; GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; buffer = g_strdup_printf ("%s%dx%d", label ? "Resolution : " : "", gst_player_video_info_get_width (video), gst_player_video_info_get_height (video)); - return buffer; + break; } case SUBTITLE_INFO_LANGUAGE: { - gchar *buffer; GstPlayerSubtitleInfo *sub = (GstPlayerSubtitleInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", gst_player_subtitle_info_get_language (sub)); - return buffer; + break; } default: - { - return NULL; - } + break; } + return buffer; } static void From 5903c7cd5ef7de077d084fa3c525371b6a0f776d Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Thu, 17 Sep 2015 22:24:44 +0900 Subject: [PATCH 128/412] playback/player: gtk-play: change to use valid macro function --- playback/player/gtk/gtk-video-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index 01921b0fce..b784f4ac0b 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -169,7 +169,7 @@ GtkWidget *gst_player_gtk_video_renderer_get_widget { GtkWidget *widget; - g_return_if_fail (GST_IS_PLAYER_GTK_VIDEO_RENDERER (self)); + g_return_val_if_fail (GST_IS_PLAYER_GTK_VIDEO_RENDERER (self), NULL); g_object_get (self, "widget", &widget, NULL); From 15d62dcfe6da2a3a845a122410a248ab5fea3a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 21 Sep 2015 14:20:24 +0200 Subject: [PATCH 129/412] playback/player: gtk-play: Run gst-indent --- playback/player/gtk/gtk-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 062fdd20cd..6815b3d718 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -636,7 +636,7 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) case AUDIO_INFO_LANGUAGE: { GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; - const gchar* lang = gst_player_audio_info_get_language (audio); + const gchar *lang = gst_player_audio_info_get_language (audio); if (lang) buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", lang); break; From 09864feff459b28b2660995fce6f059dd63d5917 Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Sun, 4 Oct 2015 17:09:44 +0900 Subject: [PATCH 130/412] playback/player: gst-play: trivial cleanup --- playback/player/gst-play/gst-play.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index d0924bd219..95b9d4fa1c 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -548,15 +548,13 @@ restore_terminal (void) static void toggle_paused (GstPlay * play) { - if (play->desired_state == GST_STATE_PLAYING) + if (play->desired_state == GST_STATE_PLAYING) { play->desired_state = GST_STATE_PAUSED; - else - play->desired_state = GST_STATE_PLAYING; - - if (play->desired_state == GST_STATE_PLAYING) - gst_player_play (play->player); - else gst_player_pause (play->player); + } else { + play->desired_state = GST_STATE_PLAYING; + gst_player_play (play->player); + } } static void @@ -568,20 +566,15 @@ relative_seek (GstPlay * play, gdouble percent) g_object_get (play->player, "position", &pos, "duration", &dur, NULL); - if (dur <= 0) - goto seek_failed; + if (dur <= 0) { + g_print ("\nCould not seek.\n"); + return; + } pos = pos + dur * percent; if (pos < 0) pos = 0; gst_player_seek (play->player, pos); - - return; - -seek_failed: - { - g_print ("\nCould not seek.\n"); - } } static void From 82b2c5361804a7599593f9cd24e4202d5b3c63ff Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Fri, 12 Jun 2015 01:47:56 +0800 Subject: [PATCH 131/412] playback/player: Add Qt bindings and player --- playback/player/qt/deployment.pri | 27 + playback/player/qt/fontawesome-webfont.ttf | Bin 0 -> 122092 bytes .../player/qt/fontawesome-webfont.ttf.txt | 10 + playback/player/qt/fontawesome.js | 214 ++++++++ playback/player/qt/main.cpp | 52 ++ playback/player/qt/main.qml | 461 ++++++++++++++++++ playback/player/qt/play.pro | 53 ++ playback/player/qt/player.cpp | 41 ++ playback/player/qt/player.h | 44 ++ playback/player/qt/qgstplayer.cpp | 435 +++++++++++++++++ playback/player/qt/qgstplayer.h | 162 ++++++ playback/player/qt/qml.qrc | 9 + playback/player/qt/quickrenderer.cpp | 56 +++ playback/player/qt/quickrenderer.h | 42 ++ 14 files changed, 1606 insertions(+) create mode 100644 playback/player/qt/deployment.pri create mode 100644 playback/player/qt/fontawesome-webfont.ttf create mode 100644 playback/player/qt/fontawesome-webfont.ttf.txt create mode 100644 playback/player/qt/fontawesome.js create mode 100644 playback/player/qt/main.cpp create mode 100644 playback/player/qt/main.qml create mode 100644 playback/player/qt/play.pro create mode 100644 playback/player/qt/player.cpp create mode 100644 playback/player/qt/player.h create mode 100644 playback/player/qt/qgstplayer.cpp create mode 100644 playback/player/qt/qgstplayer.h create mode 100644 playback/player/qt/qml.qrc create mode 100644 playback/player/qt/quickrenderer.cpp create mode 100644 playback/player/qt/quickrenderer.h diff --git a/playback/player/qt/deployment.pri b/playback/player/qt/deployment.pri new file mode 100644 index 0000000000..5441b63dc8 --- /dev/null +++ b/playback/player/qt/deployment.pri @@ -0,0 +1,27 @@ +android-no-sdk { + target.path = /data/user/qt + export(target.path) + INSTALLS += target +} else:android { + x86 { + target.path = /libs/x86 + } else: armeabi-v7a { + target.path = /libs/armeabi-v7a + } else { + target.path = /libs/armeabi + } + export(target.path) + INSTALLS += target +} else:unix { + isEmpty(target.path) { + qnx { + target.path = /tmp/$${TARGET}/bin + } else { + target.path = /opt/$${TARGET}/bin + } + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) diff --git a/playback/player/qt/fontawesome-webfont.ttf b/playback/player/qt/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ed9372f8ea0fbaa04f42630a48887e4b38945345 GIT binary patch literal 122092 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 +|+&Vdls@0SAya!5R?! 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
6YuX1v{q9=vI}{pN+P4FW!CI?#11< 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 z za(t10q%E?T+LkeP@6JiC8{J(p)eO%@n-@KLR(%hz8^PZQRs$1TA-j?sn zv*fDs;RN-Sbd{G(EYHxT7ENLglyBeA9`uyY$elH-y~txPVVcHOU)kBTtg$?n?i*6q z79T#LeeJT2?((LQSLC+qGiowIIo#8G+OIFJjiE^cJuvELk?dZ)4+|_BS;%ct4^+i? z(Js6hWWs@;rGLu7*bA5w%4;l4SA~AOLA);u7$<^sWRgm>7Bd=R6u>dT zhgHl9*vJ0Z5df{|+=cfDW-sCW(FIO!@d;GlVnH+(&K~r$9QE9o#UHDRem|pclFF*n zXv!{q?6Pu=MrTcYF{ZL&{J6EuyUE`(hk^yQlZqpfKb?y6$M^^MW1CN%+6-7k8)=M_ zg_CLvv#uJNZPlL+4@DJrlRPPqg0$$_8&pBJ7r;TwVHNFoJAV)Bz>I>JZeU}eT!q%|%7cOouZw)9K30bWj%3K2Uld-^PCG&29=; z1oofoc#Sj`6gD*#`YJU4kn7mVCvWtXhMR&O=^oL~`}c`{-ovk=XDK3=OVws66}O~P zX_yo>7Z;;&f^cS+Gn33ZzP)eD_T$I5vm3V`?|VyK9Sjf6pC=>og2INz=}j4)Vn(ju z|HLiG8XERjYHZG_cTAab$5i`v;Y@?%5f{dR3cN*dBLGE|L=Fj1A&fmjo_oAJClN>b z!9$fq3NC#!z`TRK8&f-%_bhh=?E9Csk6dOq8tmlqee|cZV)-r0$jA$P9LzC$)riH5 zM(`gS?RMkpwe3rnv=Im<4ny&WYd0G04#T=s$GSEIYTb9CfUS}I0?&_#6?AdKlQE>JP5qVK_n&X6XoB!2fm-?QW@(sbsb2m7`@ zixReEC50>{4*u?^GY=63e;Qz;EN1>a-+XuPWo0+>KRk5i)B{9SS;l{pSzeymKmQ0i zB;|ks?ip+V^ey7&S7O9^6EQxmYb(=BPIhgL4Tcr=kdsXB)-FCR5!=c+&r{tnMu|kJ zG7UVINaq|z5I#J3Du)6zi@!<|$Yji6aE!nQZL@eAXKxh0ZicVtHR@B3Gn zjSp-v8Z6PV>raGhH{9{yhUU7*Pedy>u$IAZkg1P%B92-|M#d-5-$VgXJ;e?$n=DCe z%XrPe%)zFw?=h^BpU!{33Q@+-a_Os>1Gb2ci(V4FCVEfw579qGpNhT^Q8Zbxi=}G6 znvsI~g`#_1QaBW_8K93!MTsg#FcQECPw`N6a->ru#0yN}!cZ=Z;8a^-Bto~s6pO=x z7*c{5+g)NyR1NZwTq#_KnV5560*$(uYGQ)Pv`SVDnl&;#Rhc@#a-x4+UhW3fYG;$3d7Ri`GO$do379eJ81npEkna-B`5d4!PL z%z0PmMe`K(S>pDp>}aOZq_CXitGJ zoi$pudPDZm)HE%NfEIVmVGD&ArRHt1Nv4rN8DdzDWVt-4x%LjZJjX#u3z`*aqQB4w5vfl5lO z?@&n!5M@KpoU|9{F~0l<@<}oBH2_2afJ{;@K|2v3{b(cbT2UZgvX{Y56|Djl2h|qg zD*=84@*EBU@|w0IiZG;do`6)O&aSAjU%LW*xi~5`*=WD6$z3HjxRy3=j)`STjg-jJ z=S?ll7@H+kWgCo^NS@VMkgAsJEUX5cz*@CIY4<8+3bDdMIu({2mnXi(XCFFZ+~Vl6 z!wl2ntZOLUw{mS->hPLIqc<2qfBaKQaA;$T8u`m(MdQJ$usBV zI66j=P+3`skQ-(!E;8zBTH(H{918I?JvU?ZYlr!N{(kKH%rhJbUpJ;getY30UyFq)l=doWc%XsXF-Sjw(8~ibR#>E<_B9t)v#bTu z1F*PmR+`7aQPnTjnJvXM7ZQ#LQWr-Qb-^~rM%~oQg@6hw55kfW1k@A^bZoGisUj9( z;NWt5_Pc8C8?9YDboA=+L(I7~s{Km8-#^>$+JEy?ssk$j>}J37K+pc0_q*z|?G2r) zN4G3fjk<@OwR&{(QuUZ8>XrM2I<5mf`0I@2nObHrGh0$~>r~j$jPs!Q<^#^U$Hpj^ z4IjOlyxw!b70Wd>bgmiQv{*al{u4KdW4WD|rsC14WG;H|lXgimpq2nLS zR5;j6YenH^M7=^W;u-xqF|n{g47(O0*5MNdQHvT9`vrdCScpKha{;bRRi0oGCN_GV zs7_p%jZS3JF}r{$H)dx^>$$qRkyg&lN?J^t)w+5{Hd7Xa8xv{jEmpmPBND%|EN?oa zs8z~s9LKOW2Wu;esWyNj>~&VE3bO@l^GKqZduQgu)Bid% z=LDb2RPv{9Dh_SgUFI1z;_GUeLdH2f+|c_PCtp2U=nVZGr zGB6sHgZASk77=?!r#QmQ8a`PAo_}tf^%1-4aydz7lroBkRDcJJ(@AuUgw<-jj2F;E zfFVsxVX3%qq(f4~09}1jlVZ`RSc@hV-H?N`a`!(n6W9HVlYN>fb~D$w6aR8AtYOO^ zBkND=QhI7TY^ve8QaOeWJ>xHM`lLD-CE{oP_=DtIBrf2J!7WNB)c6Yv=b89PLTojh z%xDK1A%3w@G!`vkmFQB@e$gGGM@7A84@nU|Y43%?gp5e%So_8dwkW2;vKWVLgRP zLLq_hWC-6GjKlw@ZT2GV<6`aS!u_;8Q4}AXCjyG^!u|i(?f+~0yx950F=|{pBce;v zo1{8A$8_}H*5bdl;<p-^-T}}f z+~nslT)ut-2zQu&uOIQqzvn1vb9_V=f8=N@;d_#x$M^X6`d$>^j&VLNz#U775BnV- zeT3Q{C((`&It5)X4m+y`R}Uk;bR>GA5aCN@96={RKm|mcevt>k*@Yay#%jo(kV~Sw&sJ2R<u>Es;7ha^-!CTH@}(fjV+H=6zGn&(P%Q!KmiJ=H6OkZrAi6`PQ=J7;BqCtGx=T5{NwT?v0 z?E{9S*PLx;dIPy#q>EYq=@OpjnS{t&p+h7cg8Fn7URD&URU&& zfjBf8JC0pq$UwLcF_nerZ*X9n-j^8k&j5|~uk_y_prg=hahJlxiv?J9(Qaa74?mxu zFMey#Ms{-j7~jY@icbYRe9RWJ@i8&Oi2GMTM(HIF;eW3M(SW_)Eb@>qv%8m+9bSCj zefK4H4y>)djVKN;e)7pD6P0|ouS$DTtv(5EGKT(Yt9+y<5Ys+RuEw%gq3G4d0{r5~ zwXvkVke7+X44zvKJVXGI2sQYkKpU`>!8O1_x(hR&bm-#1Cs5^D>M@%AoKlH|_ zZ6TLIUNT6j#{M5MMhg$hX@A573EzTOP1r&UB5PT^l))aw6Z}rHaYfHn^McKzS|7M| z)s$mTu4feWP2>i$cXRykO_#h{b%kOsa_QmUr-#VGwI#Jg(Te92^eln9QVP#R5Hi47^oqb5 zKxKI<|HHsSwO7Hco_vPls8Qsl5r64W6?9^lQ!D~uuSk-6)k{}h^-^Nz?%8(x?A98$ z`#_7S-I%traW?zLk&T;<9NDz-$Ugr2daGb?3QG@_qVjh+%k`>VkrCJ#v?fXp@%j-$^XDVz4@U7%O{fiZp>%M{wLt@`yRJG zNN<$kdFtR(pr~NswHGEG2sG{xsswHtw>)43tE37GRXY6i8`AG2WwDgfen*k)&=dt& z9pD%5F6~*eq=(loZ!ei-E6S}{ZL@|e+s(#ywl8TGyVrQ_}s;FG)zqkGo#nxpVrAooq(WlBFZsmhdm$zN{?YXv8@xR$Dz{WN~M_--$Q(@J|u{D)JU!C4A5HojYILwNnIE^`FN`zLOx&7A&$k(2<8xrYyMc;TOW! zg7RdxLtAD+W1CA8Mn;3c;z5vucE%d$8vtdBKWKoy>k`wCEu#qt{kX$#=8dQ%KG$^NzSu5BwGpu}T>vi}XlSO3ieOj}beW;qh z@(C50?sjmD(VT57=AY;H`iFas>1MM+&o+_y&wkOt?=X%Te|=XSf)!c2MpKz=BQcCm zag5N^rd!wFMqsE$8l+sBxKJV;;Gm$mm9v4o9+(m-jE|Zi1h5O7(#z!fPU1k}sg|31JiRKpOOulfv_fAXibIZ+rj&x`FA?gB}^BpW^J2 z&f;(sfnP1T6rThfrjRInHon*9QxLu|HDDmSKNgnH(`B5}-^UGs)aS`=EI%f@ftuIt z4A{J0TVSUS$a-?^*+m@O`ZyrKFAx@k#u^hmnDqjtsGs#KIm**95u<%^6s0saYM?Yt zC^eweC)g4P$^png^(r#R!^6#TJRP** zSl+a%ZQl8zjr>CoywYQFXSkKl?e`xdIkQX#XV$A1_<%@5nqgVGJj>{m*=H&3pNC94 zGgHDgugtSP#Y=Q~mZ8J)q<)t>Q|7O)RAo%Kz!5~KJSy-?fDK$uX#P1VD}{a?#9Gu4 z^>8BoO)IhR;_O{6{shUh0`YJL>m-MJGx4~apW@=bbdfx!(M1lqh|Yz+r^Ej%ARJ(MsT>% z7l=%c)H0Y3gI{qWEcH|d4n`5hM_?udWSy3W5p;2GM{*qj`rvvCBlU^_(blw{0bAzi zg`)Emu zLatV;Ns8P|GL@wD}s~NNRxZ!b0f0BF*+Ti9+#TR$mAA_Tt-rl+iXe&V=^%c z7dO|90NwM3;NTC?WQYJIAnNF*vCF<>%B1i{SPSM>cSMei8h{VZ|m zBBd*CKm0YLRH)U8#P?q-Qi@J6%~}~EjJ1-)ljPq-AyvwyDP(?pqg=i*E^m1KWx3*| z*X8J#|Nj09rSgmKRpP$yQc}L_OL2ep0}}83@R>x;o0$dtwjZQQ{SRclUO9r#{!XSe zd`I3gDARb!Hzw0J=eaNLm@4dh_m~j zTO5UI_E#+`W(?$Aa&XmaNcP>$-}Krla_}PC$4C#E`r1JK*I3b*QFkYCEq9OVyL-?E z$sDx7Wui_zSr0$dSBbbZIu{s_W7>=O)oG#?qPXZX%n2AZF^LJoX1_RNk?K4&RWzaC zcj~@{b4_TUXuVPs+Beldpg<#%efQ61b7glYDDH*Fvwv) zEc1a#AZSG3C+foT3)?QDiOuMgMdITQn7K{^83&YH9Co*DWVJ%Y|3O8j(Ez}N2!v(f z^0I4Ph^!})n*2+u-@oU&@tPDX5i20ZVxZVB5Sse7Skdvvj5m^)Q*4J=T(@A%q7tPQ4ywWJEcuP7CjT40jlo1IsqywB zVGMZ?H4FlEAq&Tam&)a=R}k#Hc-w3^a?!Uur{VCSxReFEH4(G%Lx&sqw>qamJH)nx zxq9iHi4Wy&u>GYP z$s_Xy^|R#jcl@^Jry&_$cmv9*2N;3ZUb@XDUjkGUyal)p@<7Z8K1Tz4(dS3H8r!g0 zVucuAnL`o|c3und*7rVJ$A8*9i&L>^RGdUPw}tf*4!z=h~?%bQD1{o*e;B>ut z?p&fHsq^L?k{UP`=TRNP`}m6gn2s~lmNU4ImQcy_x3mD^4M3rU&k+3!?ncU73G4x# zQ79_x;?JB$8oMrU$*ddET%F&}UpI9Sqw4yH{3TtimYCGNF4PS z_dr}Z`~C;)Fw$ z^-tQ3W5?=?1K@fqGB5_?Z}|FbuFRY`NmFIsA=rxV&?FkIhsc3LCW%fLF|FgDS!ar9 zHG7O*eO(5|7crLZDK$p)R2IFkpHi#qZ+lA@*o4FbZ%ttP1WnLIXFws#GA}II`Si7@ z<@}FCj%1;~<&lx6Ie9F>8IT$@(MzA7C_0G(ZT}bFKMI?{gx~mNRWynhW37ey%Mlie zFd`4=9fZ70FfRnDHy%+sG)NRWF|A8?1~2-=q+6D%3@cgLBag^ftfb2RuExWv)qlUR zoL`xuVXk1zDb@YIzv+$O%mJL~+i!8^0IooC5DsnNPh41@kl@TLJ+%TWeNSTr`e*Rx zx#D-wZD?c_#3Bg;aRx+B3TQj#R4Ow?Y4AIh;V}%WNjhfZ!Dc@3J2R%#{PC8&wsuF& zoaxKD$J&WKb=;b@Bko$c>y|f;KJ-+X)K*tsqj#4TMq+=urHXm}1=smQFaH?S1tdV0or%ibLFa3Ue!GFu*8!Mni z>0v>)QJw|^Jm}&mvM~Dx49(ElbYedw6ZGd~ra@RTk_K?|UzrK~L;S-}Kh1`*_AUQV zE74-|`f3Lmp16&B^=bZLl9ITM4X5|LYRWeCy_%lRhOvSISa24SSs(f~Z|-}K>^}P8 zC67GvNY{sC7Qc}Hax-CkN6Bvfx~#+p8J5HcDJe|4C4)i!B_|}802qL;NsuoW%k-dBpH?j7&=rH2Cnz-=nU{VULc#R%+wOU$ z{qFW>&V2oh!|_ZfQ%lw-3tl40l(_8lXF5Bd0s8+}A|TY*;h=}oGu*>(OFShMkig%P z2g{zhCwV&b7tAlPCI1LSH;r`@bRzT*y)UYhAg!>ANvonJ{~(QkmJYhsOJwq2-sj&3 zNraG%mw*5LzmUlvcx_?}NFF$ATP_=I%l5YByy-$dUd5g`gh z@-<%PG_?9+eYCIuJ(3f^Bm%7fMkY#50NtO4!cg-s4Up7;KLju$xu ze8T1em&~GP06;+mj6wF-=Mljlij{c8Lz@a`w^nJjL5Ic;ipPwcOm)ia;BcdX0HS+y zk0;1-<`E9Ztn7A!!JTf*^Nb(aXf{<0wQ^~h1sUoTwNw$x8BtK5l@Bf}_5*(5&&T+q z|K85*dxyZD!^pxjR~^`Udt+fx>(*(*TbE9EIc)`=REcDnt|8T)zbMW9=)<{7(mno0 zoo<=B$>}V);aDukZS?50k@c(AFP_y=snex^&$YI&t$F6`Escn`pZ>|7pGbRB1`^tv z3c79xHmfe6xz_;oa~&o=Q@|Gl1P%Y7*n##*8qh{9uo%N~MI%e4Fk=7-WGQCR)KE&H zI~FuU#JNZT@}W(W?!~eYC%|biX!chN7W+h6DRv9kOB@iThX_XnBW4bu=CgrCP`YWL zQL^-VM? z6qeqZJx0ao92G^LqvZOdo{|#B^u-JKf2H61I!OFgW3uloEo3INWsb>go7j3wo&IZu z;%j}~Ev*xUqOO)(>h)hK6kqA@=zc4y2?rruf2iuS`SNys0yN&8@Az!0p3J3oFK~EYA*PED6=OWS#6D zZZ9Zk?Ns<1FK3v`S#sKiAz$v5&tb3RDtv_1LX*?GO9C9a-N>Zq%IPTO->{X=Yrd_5%NV`D!CCJb zx#L(~-%~l`nJJUfJrfc)jDPUCV5p*dTsfHxij}8YioF@@pW^syw{q&`W5<@2kHa_) zIiNqrUr(d6tymi#~B6#IW$=H3S(c$`3)|6N3Yf9Ni>MmjaF!;+e zUZy2@XzGsg{HaSCuSiWC;al0SFZgDRs1)1~f510$3Y<<<@SyfD>J_7=umGUBN%^CY zgJ~W+A?3nx2Kl3kfwNbjgri)Ws7k>W2&`nAmyW0iS4DozA$F4(GoRWNXs8cWHfopj zkpCRyzr86|X95?U&lE15@=&~`CH~Me_$gAP1Tqw{u7iJFc@s(Dj6F-dbtCwlyw&Vs z?8c4X{{G=D6`jMpnQcpQ(b2y1<=js5Y$Iwd$`2CmzJSs7HJJ z51wrfCP^wMMZxGo>0i*iTu5V-B5Tidgle0>u=*8S*!{&=raPBy9e^~P=V){N|Z_8 z&0zO8^XtU~l{pY((KvxzHYknyDDw+t0HlZ(3zb%V0j(g#nwk2-jI7$)tPIu`4%u^Z z?4j`I1<4ZT-l8Ba2^R4`xPy1`AKhy4dQ$VN?CtVI6aT@pr1kj+Na+b?(d8?mf7n+~ zE8I#Pcil`J_i&2#!Z0ZR_{om!9J?bYn|yg;!QI^T{HcS(n^{)D>6lILzD(SA5y!3D zK221w`19C@7x;I6LtNkN-1#kdpm@l1luH|)8t_2D#EK_Ca2#DyKL%6_Ga4Q7b%t)bH*C;S7)_;)NEa37?L^Y%@< zMV%2cu)S1GMQ)FTa7`5~*=grpRY-D2uiAf25SxktW*v0h#Mk`WdZ$`$F!Lcl%X%f? zoOt>D(=$mMJDE>EclE#U$4tW2pL<%J5j3*BrqgP1R^RiNGn@MULGR)0I8-Ez2~-}z zmrLroVJa#1cYX>Lpyu#?^SVIkEPQUt08I;%#uC9>47y?wh%G-lcrX9b0-*XYS7@}- zp>M64{p1xRM_%#d?5Rf^E~lxud7uPCLD!af#Bl9F;&?4_dH~FKQh?^M4*o^Tp?1wS zg-v#aoKZ}kjlk=H_uqK_O%1a40SPZLv+Kya^ACPAOk|zP%~OV zHV47WdC_HC_`amDEr{ha?;+P*;7k;YAc+sI#6S8Ae_<8I^Jm0y(RRp}{fIPSl*9-^ zU3YjzaNfap=R%Mx8dU%}#yRe3EUdit42XnF?$hM}YXP0R`grxWrU4azj|Io$?LpE#PvD~b?Gc7iEMzIEa zF-FPMa!p09&uYy*mYaE3rp=a~Rig3Yz*Oc5Fk=v}eq`8Y!zr`w&9d3NIc3sY^hRyBb6bjQSa;ZtdaS9W^bC(%eKb`K>Y^gNU>T)61s%3R4o5SYX3)6#EiGp(o z`?6DAc1EHw?cjTnFA3~nB(?)9mH<5vI~{O_Sgzc-mGxN&P1 zkwWsJ%_puK>WmSIO&K{8xA}ZF?wK=H^p||4$}3y5V%P1fS7!Kqf?h%8N{V$G$dE!2 z#dSbSAy0}YLJ^09y-);Y23Sz(?=J#GFQ`j1HqjKFq?_+ydMVJapMS5Xujk}Ri71hF z@?0Sc6zV_)CU){^*8<2JA-2a8SuzERL6b+B4g!J0e{8QGTMt_72@VEq-G7O)gs zC?6tX_`oi4PO-zQgNGi(6nJq^xM>hE1QJZ0gSU#4G&2JE4b*Fx+UbZ2SGzC~2~>k{ zgBY11#(dlS+p`r$TZ%GMpT2pNjeRWlyLy8mHh$5Q{2Bi5ls;FWy?x~7m?2`QKci5k zC??3|id03X;ytBR*{M*-?eYooG+caR3=jW^!l zAK>D@qVS$+die}H{v@eWz1Fh+(4qA$uc`PaPmX8Lyu2;Mzda-v96~ZfXbDKiKvf}( zO-atKYRslIvkSF2+=9G)$LZ*h{KCnJl4j^Uf18eIboBaf`~7s62bH`Rt9kMLo=B0H z1KSzIcn)?47l(j`^Da)ele0R7@AuMXg2kX!CibhviDw)Eh6&i2pMQ1te>sZ86Fk3# z-;&^U;kKPefLyL3s-rvG!n$*33E26#JwOwJB+CY6R^!`O3I9feck#Po9u{u80?Ql>qM=mDZa(A~~X007ni zFNEOfzW6h8O@Qleo(n8A zs^qN~Y8)fa(<;~ao9E%s&&bt&JOjsnF6qPdXlAN1#9L9syCCI&azYS;M0o@~-Zi_PquO%H9tKk~!I z&heWzjqlv}x7dg?cXpI#O=z4D9`6{<)Y~Oos#m&5Ty3cjG=_&(Hovgu%&2*_D`pQL z!x5QBO1QBjX0NE3({W~vEi;I0E0gNDPwOU`f|;zNW7VpTQ7c!D>i^|`Vs02aw0>e@ zvL)S&2v&|bB&;oU0?ll|N|aiQ+q!oa|Bs_fylHviC8PmXPr~27v@kEtxAZ8n&)VxR zvNH;nd8BFP%%()M#tsiACz=jf@*v(B_1|jX;XteMq8WL0hA4hKCIk!;aHha5YhdHo zFz#!vNt_u&8s34xJe+?V>^n;raKriGnSZ|X4tIB-k{^!WONb}gen;{@ zi64-tkkKm(GR$z%3_40d;*?78X7RQK4Hy;x7rYM|!U-{s0c>L;qOLF4lIe$F@fD)< zgW*dc?;nb25+cy9TFiPeHbFxlr6+`OL4eqx8tAIUs$lWY-V~0Axr+UyTvK4P+V`;q ztNAZWaZ1lWsXFrxV)@{zeHxwAgyH~ zIU8VZV4WKNg*u?}a@8&uY2HvMclh)7N#5B6lIb*=d{U;yq*5!Ik2DyRaz)^ys3tg$ zNw*cYJY3JTI`sex^2dwcHmXeuVrn%NnzDfQtF=qb%*dHW-8g29*Phj-QF!%`tR?u4_WH7Qv4`=syHJIKL(Eiz~&54~Z{sI|U>yK||u> zKSTIqMZ$4d>-WIeb1)pWsGj00{AHsC#$z9_VG&P5q=Y2!f!gF zRO9uSUxxuxi|;Efk!84*AkLisTAvarD?fBLt6wJ?G9S=7?+nP+|$4nsy! zVJZ@I4gNNvj1`?0(RvcPL@#No&ZE3NL-l6fQeA8)-G+t2yJA-5u$=OGoId=ew#&BG^_@jo5DIor)Y?+XXhWGb=A z7nYd=)uY!AjPHAdXU>J~oW?V_7>QIc0AO@A`@vc)*d)=RFl6R}{R0CmbbeT+0zt~e zKqp7D!Nr1C7KX{BrM6gK3`1OhO{UXeRRpq36Q@lp{4r}B2$|Ws*#-P^o+a?GFBJW<=R~Kx}{U)lGKFUS(atfj2LPj7Y=&s!mhHIQt!>Q zaOpWU{_KL$?8B8CZtAHSd0^%UA4%V~KA7I|v@P?{u6LgKTX&N?bVb?d_l`W$tf}7a z))gkAJ^QyVyZ?!Y4tK8cXB}al*45noINa{v@(Lee?=-5fZDhs?%G_lrjE0hD3?x7G3Jfrb~ZE z#Qxi7-_9Hu(zfm(2)^?J6~QqLW=r#;EjKb(7GxLXf}5H2#%s(!-0yu$thpXG?w^Ea zF2fR;ZFb3#;2^phxQUbz6Zz)x4Xd0y!)#7$WVUGSD<{otviMA{G>`J?bh3K-+EeNH_-W9?ggvY`D)k1Xp!u|bk_@hZ0kSoytq8mnvW;Un#}?JU z(Jkqy9t2qdRm}yQ9`&bL!cs3y83RRFP*`z9G;A?~Eg!XnqNJP$Sq}79Ub3yn>;N}c93{OfOF_hwbY{1m9Pdy5mHOtSdtZCEl#&T>UW#hU2|s7!`E)gF3euK z6pKyQKD_75HA30yoWk6>b8`!GR?{-F?YxFMAg&84tX6Qct^dJBD z;)_IbYl*}+LuF1)OAUe>7HPeV3NBm86(AX^Olrtz0GE8xmdTUm zsj`h5=UAL(v$|L|Iog;Rv;>)=nd&V=JSLsLR2|K7rKgn3DvKJ%FVR~^r1zg6^c(c- ztTn(C&Q{N!tb}1Ln?G%^F`OuiW!X6r#hyOm^`^Tr@~cJLt+_Gr^#+|TGKO1 zvnzbLewo2x&bMS{H-=-x?9V8uuFlO0ghI`;W;SPXKh_+AN9``&$nz3UYM}4Fx%=kM z-A9A!Hm9YkWJ-;kcv_=B$$%7!N`H#BGCzhrsqfj{DMd4u zHh1wy0^#wb^z7UUaUEj5&Fdzgu3?S<+m}AGuOHJgQDYq z@d8`oFk+Ft5sZ5#Z_rD}K7%d{*pX4q!7`6Bg!*_aQ5amJbdD0Xq-S+hVFz}4OlV#7zf_1R!U@sRz_5mS z9%rPhg?_lwTo}o{7-mtIBB2HMnotIh0V@TX*dumD8RKjq1oC zp3L@MlJkv?vghx^`8|N^0$()(V`Qka`*i*8OP{K-FH?ba;#>XzQ&q9q~`kk zGXCE-Q>v~8tXC?Fz9Dv90rZN${&oMJJ^UB7%#SlSZUoI_VR}($%POC@puqd3HMU`c z$L7!S+ajUOD}7}n_Do#6E%g%Hu+7`6rI{KxsDJG~=fo)srY&X1%uif0Vnji-c=*D1 zDm+6%&Pwu)vm!7*kN^5D{HdrQ8u0y-#~w?(Wpo)q!$l@^b`s6_@qHykQ;OpfZ+;vd zF(S&`URjx&o0m6@sK;0klEhS2mX(pU+4y6|pD9zavyYHVY0X3@EueqO%J@sl%g3k8 zoW{w+?W+;3h1K&J(KkppXcnXpK~bck;u0|$SJ)zfAohzgOx;xOg%lx( z{(|d~MwyG#rRi!Z<^v3|R1l#cRHVRy0Tsh5WPqfuP{je73%e%z7xscnDOW8QEuvf|v6Qfg}y;^F1Kq2L1G7_Sf;Q-AM zE|QsQV>vmEmzHHpa@Yr>Hkl%V2)u$RVRdKFyNC-=H$$lwzrP z0;2T14Z?LMNhAuH(h4>=nGdN^LEvT&H)pBTIt|_x%yhPAG}@69LfJpmiM33Mf~*uv zmE_XF!UJqN{qv6kx=10gPGd3eP;S^Aq8pNO12nJ*8jRRW7yWVqWB@8A(B?!F3S zKoAq)CW?9^8eoc(VVn^O1(S&dfdP{Rh&FK+gCKDP=?PFI&{{^%3J}OIOr?wdj1`Cx5nQAu86oo&Ceq=r04 zubjvKdr5U{+tPSNG&IX?FyyJ32M2#P*cQ~lS9}9KTM26pWp&acg_qYu?ax7RAyf*8 zYIIgarf>j00F|Za{s2)gQnM9`30;Sv3+mtMUb0TTRu8%78jNg z#ZM0??6Of!p&*vnG>(Q`gzSYyo9SaSxR82w74nr3{OZT)YiD zN^(3fV}=~?A2R<9@4{^yx@=A9tNa&4`*M26to9P^O6}IBD<6DxSN)Z8$tsDWZ!pva zAoO40VaRI>3WsN*-@N`Z(aP-^O*sp++J>xxM|bakK0mWTDwnfa7emYp#vZAmiNW%R zXP_noJVX@{Q|JqY$l&u)3m3Yh9>b#9LMLo|cwmtP8(|o|RV(t~Kwx|5w2e;*pMzi( zOD1&ih0{drEAu8*ubo;sZ%TL1Xr`!n-Ic>62I=HHhq&m_q?;ey_V?{$FAFeAA{Vd3 ztjnwx+tM6m<7)H4*#F)D5dWhG5nGc1EB3r-m5r09RKRD!7=|&-3luv%c3K*n1cU*_4$#al;-CQ%4X}$e7a?E;QLr8c ziAhp_eA3@$D-?f%D}PSnHh<*hpGC2_pP4WxSvLE_uD<7)SZ|_NB0A3h*!AITRQ!`d zs0+F!(aRB`u244nZ<9{Pgu1=S`;qXtAFaR-EsT(&0oy)7&UZNC%_3j|nFz%}BORh- zM8ljM{^<58Yc@VSk=a<@_jvHq4#M%@|7G1%%gUtnB~_XXwXFsKeu=27p?X|m$GQo} zHpNFVb;W0XXqj(r{4@Vu*DbHC6c+~5{k2`?J{pjD&i9&ynRvbEO3^_&Hh6SY9;BQE z2%!~ZLkd%+8_DwIx&f*Ua8!b{De#B=`UX|IpgB>GTmRpr`Xw|*G`n*S%wKLuMW;kL zZ2^ZXt05!J>1)f)Y4f>EmY~&}<#GhtI)z={bYUaMD^$tJZS%oK5~5Xpd4#anmE{G& z2+eGf{0n!@8BtS7WSGH`?l1&8ng6;Gr|u(%-D)?R?Y2~h(`GYh)n;rv`U|l}V!gsn zM{08C1@%&Gc5^S>O1*q+;QwM)+uAWK;>@iLHgqBqHu*O*HZAIx8kQgREn5~3UVkLNPC zup$8c&bv3TrzP)=P8GC=(QXzLdKL}-qf>=&zfw_9yC!idI?bnicP}%Pu8=p@XmuuX z1cWidGo0jGO**00K&51zAPgD=&xL-?O%Qcc36gRpL)XS|hinemga&6HYV{pGweVTeZBi>fAQqDO(QOGjGQwrwCJJko-Zd?M-HU> z$bp++8=v#i{)vIAsnai6w!8SnDQ%e*X>LnS`J4u=ZsB1doHLd79PzXQSW{~83eMqA zDHki|0CdG5@{i-mAU}J}5TOOHB9(RVq;$eF(@B8_yCL@0lpOP;15<=BL%6_A{R>%G zeBd*$FC^!f0$(xABZjV^!ZRe?ww}>WneGe~+DS+Glm<&_aL9;w$BakjvRv2w3m)$> zDl0OVj$d}*@a)CQb7fw0hA&#uk~#0d?7>Jf^3i>@iWI+tNl`MsJdMWJSgddwm$gZ? z-Q%1xjUyvfT-I=P-rkw3nhF*_Hl56WWXVFibwOLx{VV3&Id7F|a@mB^`k;LW^YLKR znb7V9Uoz#Zb;CO*Ixh>ekJ4^?XzC*PimQkoY!VP{av3dJ30z-4sAAsU$7Sh~hoDY*8$<3@J!-|?^T-*t|>0@?7+$H^wYU;jN)hJKM1 zgk1FMO#j^w?ri7)u=n(e!gYkeHsRXbL+4$Q@cj_n0krKk=iQ7j?o%iUhJPCUX@ysv zde6{3Ah@ITYiZvIh9TYqA7Qp|LLvYf-$2`pATOk02uY(k=0FsN>63~UD51IbIoq=G-i@8VC5XsF>2={?U|`tC%oKx7(RI^*(_)Y}eU_L0#a2x}sbktiq3I7Z?P zX=mKW`Jawo^X2I3JtV$u*52oc?6{ThvlOY7PQp#zvh6q#&WkfmxvzREpOt#}Jp|4! zCDQ1l@csk(Srl;aivf)l=0<@dh5E7Gz;+CyZRdQywSk4!;DNV{g@XpRX$telCI%f3 zEY^r(f|67zz|H8d7m-i!xWbKZwiwL)erPV~d3H95y_UYY7O%KT9B^>~SKyxxV=DtS%leM{Ai&sQR^!#^f6EQh|JQL zN!Qsc$MNsp_aJ}bcNX?-TF|$A90}gH?VI~&lVydzt-7u6@vr}XoqY#jR8{u(-20}b z_d)_8lR^*zB$G@E3rQ$OK@|Dc4vtv#fV-e^NQIJ7}(d@?UI8rgieacuffaO= z29_95Sukd(8x(2!Vk-c!$`w;*j6Dh1x4;=1uDj8wgi0yKQHO|!A~jvSsElz5X~iWb zmEl@4LQBMm&Z%GJ^yAVVZ(vKmQss^`DLW&4K&Eo|q1e7r!Bv6u1si+)>6Zupw*G$1|4Wp&eA(gQ14mzb-NZi8rsU`-eeco3(<`RpsiNCL%ocui z(Zt6rh2|?u!uvegXJCXdR*HEi^07sa?Ad$An(Yy98-^E@mWQSkc9 z*)|f!zU8mlMlM`F^TN@Y%a_m8=gnNspYu{I^ikSuBMJ*g*xC{kzaqD~Ux zf5pvw){Z&t6?1m%m?O2$*}?Ynoc8-L``59deCtK`9fS@Lpn$j32tlsI%kZ_}$MS&s z-3Y#iTe1FIgPYwCffjDl?a(~|j_Vh7ujuyaUc>ny=GPeJh>)pYP$mm*b6YgJhJg^& zO?S!ncJ+$D0w}rYPwgKa43zxnqSSEuHjUA>qpda3T0u^WGKKQCn-^~fR_Zan@ow=w*p)exNVZaK!6vEa&Q)6NJ{=x)&3nfE@xj2n9Q=zE|FNG`F(>~xq=n7w{FHy zKRj*2^~#2jka%cMn$1ZWvGMWomSl4{8?Wyh9}>c94SnMg2D>bJ zmKDVsL(R#S1pF+?#&BgFvm{1DDlh5#wjXBI%EoA^w;oT3;@*kG-elMBH*?l{;6U_+ zYmA1`;~jWz>u`m#vNtPI9@9r5{BEOx%^S%^Z2kD<-Db%KL0QoeyIDk-^45cF=$TK< z%Fkop;^C)18wh!;`&dELoyr#<=d$G&II1E6H3q&!y^@cItgS0C&oAbX_3-@S_H&D$*B^bVVzAPC zaK(s0(shepWp!;Mm%Q@IlB{RgVDbj%lCsD#9qe{ly_`_`G(TS|~hRc*2J6?C+Q0C!9%4Q$l>!|4; zseGhV%&nK+*|+P~^-XN-p@az|46~Y*KFYqS*B)i|!z)Hio87Tbx$*L8Y!M%NVHm6B z@pGov&r`~j4lZIPugQcBmtMbS&Gn`FpKqQu z>%!L35mLuhciwqbyEHI2)9K8RZr9(peq{Tk0&86(Cet*Z-hwgudNG@(+g@06{I`AQ z|LU*KRY7OONduJ=jV5Re?$msg7Joy0n)oPRq{Yi*#z%qs@0ktD&uqPrwe`$GN9e<| z>#iEa2E2T4`#q7j?%cvZPDo2j=*Xl9AW#b5j_>Hpo}jNXNtkB|^ICkjZas5mtN(Re z)tmkBsOP`Er~TfMC6*6Cdvj0+WnxXC2aTsU;z;sNA4ouy%caM`r0LVX9EeO zYS#4j5ndMWV+lSM55q4D5s`?a{WEM{tUwldbgp4s!n3ZRRq(!DAhW9D9S+G5|QrX2Oj*vFCS1YFs{oi^^ zIqCDQ>Gpqj#t=(n#^4N65thLj8G|iDW(>|4OzMUqxITX@>sLQz=XhUFC*Sz29&sZ6 z%;y)Wxn(zHT@nI`+zI&w)ww(MnQb5n_jrx+dmvYO?a}A^E`|!i~B1M{y_6Pm?06NKS!kfclVKmqz zX6HZ&ddNwgDbVI5%_-=Brb|?lI@!R!9SEM-bH;csa0iotBEwpyUvvD_#>wmEdmHM# z^X{Lt?k`6ls(pX5A-%UbGGUmk{CM-y&u(R@N9Mh^an;ggTc*`5y`?IxJ|@0z%wHcG z+Bn>5j!NkDr>ADTs_09lJ%?Uj{ot|U>GPH@nK!-o`3D}{d&O{lpSXm`WZQL4|H);C zS@gBHZ`*$RwKvLDE!k=Du~)@EbTm6RJ0U7Ab<~&(uVnX$(&n@+AbqLW*BOWtZ>n`L z4$(FO7?NnG04zduUxDPHVC5|Y9OA`Vq0?N|WDxHfpb>(k4qNkdIY6{bnm!3Wdfa)U zjf)oA4p9vqUtz1@idoXzLVG*C*M&29Xfs*5pMtc5ojfs{?>?k%pG5bH3e)4#&F++b zQqNc@x{mIQ>{6?uOU{<&oBAY&M&}`Lzm&*=(RvBeeELPi_D#$-^+OT6m0RWipCrni z5fxhPTY|>A2_rT!{}sw6{z87KpxVY5zNaKp0p{ouZ2!64S1WkJsyBhQxC4JLBdx&cnaM zI$#W5?%IR_nhw59IYJcnfBqCFiMzLd_{kR1w6#Dn67d6oAro(PBv>Gd6gwv-33trI zG28!;BumJKh)n>S;?T$~(ocjDU?)QU*Tf2z&4#874;{(|;zD_g z^4`$U;VH@+%7?M=M1cPgi5`!w(=XWn#C)0VzKzn&(djI4ID(0bVkCBkCX4F45mQ)k zAP|DVSi&Ni4jaqNAgSQ4>7qAMG4_H%Xi1R|=rhSxAV6A#f!o@YCh>}yGpQn4W-=Y; zp;IdDrrsudMQ=o#bWL30q ztDc+VIK-}TeQbz?C-N-j6mz@@FTeN7%z!k8Og!cnZiqgrlP6hl?E_B|iL-V(RfemO|2+_EP}Cpsyjjo^SSn*I zh)(R!AfOMe(|xbq+z~!{(TnvDe-|A-e*9%KUD>Ifx=XvZ!^e$FzVX=#LHDm(R+HO@ z>wJ?xN$Y3O_e<`u#8-ObQf2b|vv2XryAS?+!uM2?@+Y;wPOS>uE+7NzC{cgRx*xj=7It$h+(2BjsX^>%pi2m;2Oo#m-1A9P$; zg{-FaO%4y7T$J0n?0dGP&@y=pr4V*zZUWnA2(s0xv6^lDO zs5P8ase4vkGZWfG)ut!G$HNURHy-{`Y5Vc;jU)Z@E=vLbUf-0VGfVs9Et9AF3LC@b_)PKER=GU9Z{Oi^dH(pK z%y2}72t!kolcM!ueKXVIX748AawgnPbS;BYW>GC@!W8U zGLi3!Pf1Ns^472V=;wiBDzdUF#ti%!bGXj)*gJ1AMqxgK(=-;ZGZ8~INwo(bb#TKD z?WAta#SCWHI7JKVW3%YY2uk0geDJ|(+W*6zBDMupw_5o&mQIUQD9R)5MG1b79FmVISd?f#Gr0Z9 z5^)n6{1#ws0Xrb_mc(e^Q`h~N%>Xtgwkf5bNKCom+R5RG%KEm=%JFw+$Mj;e+E%iV z2DVwb5E=Rn=+um-%8C=EoH>P%o^|HJCF^}{I~*z7=!KwCgkfJuVNpnU2f zU9?oTYBwrexAjbGuDQ?fm^fp3$D?!}rk=m)U%OoS z$2Mw#CEY-UaY-_}?Bi`L;qsZgqf_H&Em|}yJ~evx1?@PEz*Srk8W+ngRQgh_sVzgzZZo^v-G#;d~}_za~YcH){Di`+6XNb z8@a4=<6FO9Kp&pY zAc0f6R1)Z*CQZ30y=Nr|6#dVYfJRW%-$S|T)fYopB#?&Dl@YN*eHy6)CEjWaZlnv#VJe^ZN?b`m`?g&JdVv%3sutP{oQ zO(MrL^uNV>%O4OJ!Vrw8iFgJ+8Uk-6tC*}{Cll4Y!y=$qY{40zt@W9 zS7{LD$300AZml0a^7!LN4zry0doZnO_0_LiSML*t(EOL%=FYv1SL~r)vPXDG|6H$} z-4)$~Om&N1BUVQsP&&cqOMpn}j)RMtbMazG-8^5q<@3|qO4a@b1|xmc`0-InJEoO_ z29|C+{rMJLir07kqI_c_+E58OtTVu`^*cC+skNYMIHeRsSM4=KiD?-hB!GmRIHeRF z3cMmTiAgGB**NUNaHE5iWYim~3#-%|(LvLgu}60sSDx5c`QiEF%H~mlqxVcOhphPg z);S+e75LMw<&{5WJhxgnDmwimr|{q2^2rv7MZRtO_*PV;)QSp(1Fl8bKGx3^R8!R1 zvd*fr5a-*T(&yBx#`?{l%)Ry7d!y7oSkXPy*s2g8FiP1J->+BOHu2fsp42DpI4jSd zw_5q7-GpO))kWC{7u4ZDwX=`0sKe>HhW}89z?uI@c!Puq`>j^3Dh2L|X<#u#;R5@* za4s4zhqrXE8dPDr^3$`Q?hV|If`bFL8+Bd(%S}nWSj67bdspvzOY4<7pdjvsoAw0c zb?fB79;sACK4I-i%}{sGDD9~k3$BX#EzOSE!!jOukwx{%SYQ{u@$VWMLMAr`(9&(J zbz5nB7wRb$+ejY6#qsn{#07y#Y!H=fF{-F0TJf2FGpZI}WT?dWD$r09fr*_!u-h10 zH46SE4lf3S7;UKe-Ep=i==~*)x3Q7wJqAvCQ#Lr;Y(59 z7kZA|G+rtH660?v_FysoLJl>DKsg)<#}*aax+XO?u|5tmiv}4$fK~bP$4HxVi_25`O|^S5B#ZdrniWoSu8~foadNl4l=b@tgJf`;_yWRrft= zYVA+-WMaBFSE4;8bsid=-_gKY#<2kFnl8|kQ{)H(qJ}&jT~kaVMKlBG*gzTKwKSpy z)G1xZ+ug@}Mss;_MxE8w3o^ljiHj(pun@K@ef}7#-Osh3hX?`>9%ORuZ zm;07)K5(GJLT-i@Yi8SyOe>%C^_r3r_D;yA)sKJO@dP}kWP(KnTMW3&9{ckq{&!7#A`1>)Kw&J|b4{xOPF~9`QQR+7wKF(iKZ~zEQuCMepc0an8>A z(aZXTW`(6C4zOlJYT>|Xru2ph!$)5?t)h5Fd46idhff_)&h_fkD=m>n6^tL4C_V0c z^{$4(rOKw6FYLJG%8HVldj{4FoH)j62z{{c^e@@8v=I%HR$pBJCA zo2{1`GDeqFg@;CYZvJL_ZSD4N6ln9t|F97xnk5~aH}9bG%>#egp;Rq*&O6Ah*M%5D zEdz6GWrJ3G8m~29KPpj*NQhr;>nb7KZ3_#n=?X(>9hMUm14XyUej&@=Hkcm8x3k zP@j}B9k*jjCbT7Yv%rr3`+q@ds)D2%j2icoYl%KRXyPJRNk=*{GeZDQ*SG6@(a?e@ z2GR`~T{>hILRKN&>!9fzmiv>+gCvS*A26kR7=d-_rge(tejA4hUA+Gn$iY}u2fi-n&* zAD3(gTZ*!&>>7`$D(yl?Z3-42@uxBT1kun!G{i!jGfYQgbf>Nj2k>IEhvVwj+O;PP zQ$BmBqavjrr?Iz!!B>xPFej4l+KPuhgSmy06m(jgc_s?37F^h~n6MKJwso5&$6_m> zy>L9Dm}%6twkl_f*%(Du*5glRV~Ultt*zLV9mVFeQHsv_zEP)Cw6?Uiw@Rcj2yT;f z7mz%D)V83OpqDfrWDBQ*h z_nx_cIo9{$(I~_WTL0qG_LfC27!F4D?;my0@WLXgW$BA;t>TR+c9g_N(GQ!0J<=9^ z`;XEOKKMlGcfF?ihk5y2eH*o+1E+7o$SUZz$?s-MeNvX~r)+g}w@@BYNu3u!hnCVQ zG|5=S)kv`5(8YY11)~?8Oj+V^835PZ#nrF^ldfaoGNbawzmio{o(%BizM-U$RG@%fd{DePr z)MW*QF++8aliaBONDsP8K|6GE(?jp_hgpQv^k7~^Tj<~inailps$dw3Ta*QUA}!Y4?;ur2xOJ}?A2Mp=K@DwkpU>#{N6+t)3N0%Sc75)g1 zw7c?%xCTK*=v#DGl0x1FL3P=KX~0h>>9lgeO!-HAA|8sD?7~fT7x|?0gDMyg!3a2{ z0k=UaZ09d%gARhHvT6DZ0u^6a$}mA?C>iJy6ZvVq1w8~@q1>3%{MKDX9?UWx%2YN{tOp7iBc}s!2P;gHX zszoiQ7A{qkL4!xw3d&pu-l}SRj11(Gv!UxEp&v> zAvb~rOgiedlj0EJT141{Abgh&cQWgtQyOL{N{`$KmK61UnySOC3F*0Ez9tAy?N8<) zeK4KZT@v&oy(@h+PlRg~7zGwGU&AYDsC z)HBLa3b$BF3$#Fi>IlXM_cHh=2kjbMFs|;p9${DoU_?>G^oU0zM@MdtN{kv8wKe*>=oQhQ#|(&hB({HSOPn>H$Ipo0n-HJi zf++bFiS0>wNehx%lg=imBv&V&>UCYO{V4-drlo93Iht}NH9fT~)s^~8>KDBedspXDe#SD)a))KV<&%t}j71q|GG}CN z&s4IOWt{}4)K~o$_1lk&=@WA1=X~A2qJM4w9sN&Vl6XnwC0j4~Xh6k)?YZXMg@fV; z?HY7qaPHt0gHH{~7}7H2lf26OSBR%Bae`@U#G z(Hq56iqDlSDNQeRmF^vC9D2?0>fuk9MVAdNTT*tud_hHYMOnqX5mQFIG_v=|Wg{OQ z`SqxYqm&vEGw*T^Jm!BOoc1**VWn&&4vwzIFvHizZj;$U0(iQqE zrd;vEIM2A_SJqv*>8j|f)?9UVyk~stgxCpn6V6;c?dr8xpPraJan8gauDN6KfhiNG z?7cSp+LCLRTzmAo-q$r;_smq?)B#hknYwW5_Nm`bE1R}p+H2F!UqA8sUDMO151ZaH zUAdv+h8^@bBYDQM8DCUY&g?&Pt9`6}h5d)>YpNfqK2(!lb64$*+UIJ2mUHE`vQjs= z?uB}D{rvi;8xk9qG@Nj7$4JK_$7{3nvkGTTn{}cwywTP8bmKW^wex=G*{1ZShNg8* z2VG{@4A&#BbM7K{i~FD_-m}4TZuXqnN4$C7g}w~mM&Chyq5rACfWVG91Lka>+i&j9 zd3nth^GoJ0od5of{cm*LxOYL~g0Tx0+?0OPft!?tl?y!!*DZW);n|xjZ+6{$Zqfe5 z)r-GaGGWQOCEqVCT+9k-vo zW5OK=?;LmM3wO=CYu8;r-#z8-l`E51o?W$O)%UAQR?k}f+&w948t(0X@2Rz)+;{2$ z*8@A(Wvm;!ZpC`zdguC&HjLiz!h=&D+`DnW#`zo1J(RX-#-`SXS3eT_$m&PhH&5Jr z{L#8ck8hc^<&~}JTQ_Xu+bXu5*nZ-%B|EZq%-C`0@#@FV?CiI*Vdu#w!k>6$mu{D9 z*UnvSPi}uIe7AA;%-#1tZGF1#&(VMO|M~1Q+n>#N_L=8KKL61Rd-tw*vF)XkFAshB zxmQYF+4^e9t1Yj-*1Dl}Z|k@Fvi5D?U$lSu{?@-t{L34!RlMH+^=IA)ePhKNuN+7| zP#S_#vtB{R(AMtC}L&8 zCItnHlKO49(1O7u2trCENsDq?z@)e!8bLvjI{vPikf(VB0ja zN%shg*34#HUwv9Lhv?$jLCqYd8^Au%%#pfb+^m@o=8h+XHx-IivUt-3tHS3MZ! z5jZt-Lca**6E+zqxH4P`x)x=xMC>laIRgCFPuA6mxYAJyH(dOv zBl7vZdLx(`gb1cu2MBLB7_w7sP%K`gQH$_Rq7EA2W``@eJ0N`|08=m)7of;igU~*$ zPQ7rUR_I=JH)FaBhtOFi;G z{P-Yvf^ANR7Xx*7K_1o$aQCm&tzr_c>lh&Y4X-~zGGizUW8rw!0SrXAskK^nH;aY@ z`&c|{70(h_BJN5hvtBF(cY}LF+98P$Nyja#3~cgbfvd0|1Tgexm#_hl|D20w?dd(X zJeH6B?*dlHY!Et943R0NY$)Wy3}eF~AEq3TQ4QvLh!jqVGR?TWyEt6Rtt7i?&!Dg{W zye`tjT+EFb&1_Jef9fJ|?&V{C7GQJOT-*n0#(v+8YyrE8Eo3+2Ap91#h%IJI*iwj> zzLhO!x3LxMc6JB5likJcW-Hk$wwm3;*06ioT6Q11pFP0VvGr^Ndys8p53x<`VaTi4 z%pPT1*jBcUZD)_M9qe(olRd$9u_xJ6Y&UzF{h2+(o@LLm=h+Kv58KOLWG}Io*(>Z- zh~M7F_OrjR*Vyaq4cv%2z}{j9+1u<8JIs!-ci2&Oj2&n1vJ>n*c9Q*-z0W>ir`U(= zBla=-gni0Rv(MP)>~HJ~_9gp@{hfUc2H$VlKiC=e9XreZ$-Za*Vn48dvvcf6cAovj zerCV0Hg_r2JdVfn1fIx~ zbQwIE_ku+CRNkBS;c2`tPv;h%fyWKAcsB3Hb9jG#2_L`*@?4A>gZU7i$Mddnj53|_@&ayzfq9p^Q?mdm`3*YgJM;Inumck(9g;%@HY zv$>c1xSt3396p!NMB)w#ijF4AomO`XZT<-{%BBV$u3a@L#NU>6!6fY%6iBgi3EcKF7q*UD+Q^4iO z%NvnLx-~B^sOM{TtELxddZDJ~wo%Lj&x7z0Ys=hSH}>Zu0n>^#Pyk)z>+kO=f>XmDBF6m$|>e zL}rK2&)tniuiWIGBb(;C-Az>vms#lUfM0Ug)fs(`dY9cP)wt^oey`ovpl@(D$!5eR zSJ|C@z2DI!>%DG!ZFsGFuFDAnIh%tPW57dh28XNKJul4Tv^Q7PIJ`AZ8EJZCyWixM z>%6kB!Aw~Z5jx#9jruyLy*?C$sr59tU9vB9j@ub%lB8J_w%k%tI z4YH%Y!5=Eja~-w*hEVv`yQ-XWoj+VP*2pfu>bUJ4$enr8)ken(xip2`yDaMdE5 z3a6Px*vLA2jZC#xHOEmayGGwBVJZ>{XOp{8=n=C6GO7fT zP~Z@UtIn;0`D(nf?D93Z{Sg|NiWMOMR867Pc3047_j=uPMNNjOMv)7%Y7TfpRfE6? zQALDD$d1o3U#_YPIGy2|+1F%uI-})zHBNhzy(?S#dPkjK@09Iz=p$a)EH?wu&>A6R zsByY|ayaVC<#5%DycyVvNoyK3D{rW$us6WZI@WUqJ0{?K531w38# zNcjW0`{&BCKLSPfcqqymblC7ZV4>_)6ARNUl!YiQ<8x%M-+>fkG<$>F>zEJpwL3$A z@l`dz3xBwOuEP(!R4bm4jL=@#!c~l~LgHSx)F?OW(VKl{Ez7V$YT4-wtD$PGL&f^#&;~SPm5SZMNd4U;OaV`b z(5XUVROGC>h>@{9Ttoy>J)8obozSc3-2<2#Sh23#s-xtjuK_R4?fFDiiX%6++Jpz9m9=*>#M-HXdsj3E| zHUi_^ULY3_IJw&iis5xM%KipKSl)q~p5dSV35Z2AXfHstyLs93lzlutVenLicQq@4!8m1aWU7_EAvb%hGpW6#m z!$*LbgbAG|IIw2fq4c&{i`r z-HW!jgCc_Fg@U#>fM^Ds)n?EIv~#^2DXLgxgNCkf)v98uiH6durrI#T`WxInRK9HX z1sr~AF*6|*rD&|dKqbodgT5(6|#~Z$j#JSB-3Jl4~0s z{;)dA5>lBZBkMB8fd*0U`ntS2Ii}VfKtoj_pZHQ`#`bVg@vnyy@UMrRS|#)%e3)t! zfM(T96jj7eK~1%?uMxwB(P{UPCJ@CR-sG;Pa*5s;uHn1Dx6s)Ew_nX#m}*qp8Krf( zP#86=0i(aOIaDJBsF>*PB#`Pbsv8+3d#F6mLtPX`v@ROZ;}a7QgRco0G1Os%a7j&a zgQ*^)yT@K@2ALChVWznRfkg^~AT7Y_S~KbxqnF)@9#kubhzuc^GpdW;X#@zwL>(+d zr`OkjiiHqJ6^6@3A~wKEeU-JiG_2dm66On_N22>WJV5I}wQ54Jl7etgVE%lnJBM5& zV*sTHX_gat(MS^=qp!gsJ8L6@1C5%S7#gCgKwg1E0f(;vHR=VilWE|YS5pfDrH$Hx z0tf`@;i4i)4<{l}-GKywYVbEXZTcFYufPc01j!6lsVY^ZprSsEj&ZLj8XVKZM*fJd zppZVc@MesrfofSD+BA!P9-29XKEk;x3{|G77I=e6HAp%pfI;GgITq~oUVD?V4s+T@ zuVk-v>Fe@~^CPPr{%R0*P-C?jKnb1RFu%}*Qvq%{&@lq@kWEK5jV->$W)B86cfjkL9l)S~=>Sm0ZL9-XcFKDC&;agt zcqCtdnzpFOM2j2899gOk)blxwhPkq%+Kq*S3;zgEY>gCUM|V&uH@Ouis09d)!A^Aw zPF_WkwQkG@#PUO{{Nj@EIhcxLRx0v@P$hX0>c}P>s@&vcrB4BUsI5wD^eLiGF?~wt zBbIHI`6KdB*YVJsFZ+838=JGUsaeVAgaj(h{8N1QfRG0^#!aM@X&!Z3d<`Ymp0p%l^!h9rCm`@P-1d&e=`C8sai6v@f{wRX8 z5}cLbtOREzI4i+f3C>DzR!XUWQYs(_aLM9B5CvNHMr#p{7Hi(h{Lvx=ffi^tVQF(m`eZ3M0%6D3nj5G4dr zLJ))(g@hM{gcpT`7lniug@hM{gcpS+1W`&5r367pQ3#~Kr%+#K70n2wfJt}|1R;eD zNP!O^Y=jgxLJC_R(y-+r4I5#EjWEJS7-7pRHO!GgOJO9Guu)t#LJ1q8gpE+bMkrw; zl&}#>*zze6D->3J9Wr?l-V&_Su?ry8j2p&Q&b{ literal 0 HcmV?d00001 diff --git a/playback/player/qt/fontawesome-webfont.ttf.txt b/playback/player/qt/fontawesome-webfont.ttf.txt new file mode 100644 index 0000000000..946b3cb1b1 --- /dev/null +++ b/playback/player/qt/fontawesome-webfont.ttf.txt @@ -0,0 +1,10 @@ + +Font Awesome + +URL: http://fontawesome.io + +Font License + +License: SIL OFL 1.1 +URL: http://scripts.sil.org/OFL + diff --git a/playback/player/qt/fontawesome.js b/playback/player/qt/fontawesome.js new file mode 100644 index 0000000000..8d135e13b1 --- /dev/null +++ b/playback/player/qt/fontawesome.js @@ -0,0 +1,214 @@ +var Icon = { + Glass : "\uf000", + Music : "\uf001", + Search : "\uf002", + Envelope : "\uf003", + Heart : "\uf004", + Star : "\uf005", + StarEmpty : "\uf006", + User : "\uf007", + Film : "\uf008", + ThLarge : "\uf009", + Th : "\uf00a", + ThList : "\uf00b", + Ok : "\uf00c", + Remove : "\uf00d", + ZoomIn : "\uf00e", + ZoomOut : "\uf010", + Off : "\uf011", + Signal : "\uf012", + Cog : "\uf013", + Trash : "\uf014", + Home : "\uf015", + File : "\uf016", + Time : "\uf017", + Road : "\uf018", + DownloadAlt : "\uf019", + Download : "\uf01a", + Upload : "\uf01b", + Inbox : "\uf01c", + PlayCircle : "\uf01d", + Repeat : "\uf01e", + Refresh : "\uf021", + ListAlt : "\uf022", + Lock : "\uf023", + Flag : "\uf024", + Headphones : "\uf025", + VolumeOff : "\uf026", + VolumeDown : "\uf027", + VolumeUp : "\uf028", + Qrcode : "\uf029", + Barcode : "\uf02a", + Tag : "\uf02b", + Tags : "\uf02c", + Book : "\uf02d", + Bookmark : "\uf02e", + Print : "\uf02f", + Camera : "\uf030", + Font : "\uf031", + Bold : "\uf032", + Italic : "\uf033", + TextHeight : "\uf034", + TextWidth : "\uf035", + AlignLeft : "\uf036", + AlignCenter : "\uf037", + AlignRight : "\uf038", + AlignJustify : "\uf039", + List : "\uf03a", + IndentLeft : "\uf03b", + IndentRight : "\uf03c", + FacetimeVideo : "\uf03d", + Picture : "\uf03e", + Pencil : "\uf040", + MapMarker : "\uf041", + Adjust : "\uf042", + Tint : "\uf043", + Edit : "\uf044", + Share : "\uf045", + Check : "\uf046", + Move : "\uf047", + StepBackward : "\uf048", + StepForward : "\uf049", + Backward : "\uf04a", + Play : "\uf04b", + Pause : "\uf04c", + Stop : "\uf04d", + Forward : "\uf04e", + FastForward : "\uf050", + StepForward : "\uf051", + Eject : "\uf052", + ChevronLeft : "\uf053", + ChevronRight : "\uf054", + PlusSign : "\uf055", + MinusSign : "\uf056", + RemoveSign : "\uf057", + OkSign : "\uf058", + QuestionSign : "\uf059", + InfoSign : "\uf05a", + Screenshot : "\uf05b", + RemoveCircle : "\uf05c", + OkCircle : "\uf05d", + BanCircle : "\uf05e", + ArrowLeft : "\uf060", + ArrowRight : "\uf061", + ArrowUp : "\uf062", + ArrowDown : "\uf063", + ShareAlt : "\uf064", + ResizeFull : "\uf065", + ResizeSmall : "\uf066", + Plus : "\uf067", + Minus : "\uf068", + Asterish : "\uf069", + ExclamationSign : "\uf06a", + Gift : "\uf06b", + Leave : "\uf06c", + Fire : "\uf06d", + EyeOpen : "\uf06e", + EyeClose : "\uf070", + WarningSign : "\uf071", + Plane : "\uf072", + Calendar : "\uf073", + Random : "\uf074", + Comment : "\uf075", + Magnet : "\uf076", + ChevronUp : "\uf077", + ChevronDown : "\uf078", + Retweet : "\uf079", + ShoppingCart : "\uf07a", + FolderClose : "\uf07b", + FolderOpen : "\uf07c", + ResizeVertical : "\uf07d", + ResizeHorizontal : "\uf07e", + BarChart : "\uf080", + TwitterSign : "\uf081", + FacebookSign : "\uf082", + CameraRetro : "\uf083", + Key : "\uf084", + Cogs : "\uf085", + Comments : "\uf086", + ThumbsUp : "\uf087", + ThumbsDown : "\uf088", + StarHalf : "\uf089", + HeartEmpty : "\uf08a", + Signout : "\uf08b", + LinkedinSign : "\uf08c", + Pushpin : "\uf08d", + ExternalLink : "\uf08e", + Signin : "\uf090", + Trophy : "\uf091", + GithubSign : "\uf092", + UploadAlt : "\uf093", + Lemon : "\uf094", + Phone : "\uf095", + CheckEmpty : "\uf096", + BookmarkEmpty : "\uf097", + PhoneSign : "\uf098", + Twitter : "\uf099", + Facebook : "\uf09a", + Github : "\uf09b", + Unlock : "\uf09c", + CreditCard : "\uf09d", + Rss : "\uf09e", + Hdd : "\uf0a0", + Bullhorn : "\uf0a1", + Bell : "\uf0a2", + Certificate : "\uf0a3", + HandRight : "\uf0a4", + HandLeft : "\uf0a5", + HandUp : "\uf0a6", + HandDown : "\uf0a7", + CircleArrowLeft : "\uf0a8", + CircleArrowRight : "\uf0a9", + CircleArrowUp : "\uf0aa", + CircleArrowDown : "\uf0ab", + Globe : "\uf0ac", + Wrench : "\uf0ad", + Tasks : "\uf0ae", + Filter : "\uf0b0", + Briefcase : "\uf0b1", + Fullscreen : "\uf0b2", + Group : "\uf0c0", + Link : "\uf0c1", + Cloud : "\uf0c2", + Beaker : "\uf0c3", + Cut : "\uf0c4", + Copy : "\uf0c5", + PaperClip : "\uf0c6", + Save : "\uf0c7", + SignBlank : "\uf0c8", + Reorder : "\uf0c9", + ListUl : "\uf0ca", + ListOl : "\uf0cb", + Strikethrough : "\uf0cc", + Underline : "\uf0cd", + Table : "\uf0ce", + Magic : "\uf0d0", + Truck : "\uf0d1", + Pinterest : "\uf0d2", + PinterestSign : "\uf0d3", + GooglePlusSign : "\uf0d4", + GooglePlus : "\uf0d5", + Money : "\uf0d6", + CaretDown : "\uf0d7", + CaretUp : "\uf0d8", + CaretLeft : "\uf0d9", + CaretRight : "\uf0da", + Columns : "\uf0db", + Sort : "\uf0dc", + SortDown : "\uf0dd", + SortUp : "\uf0de", + EnvelopeAlt : "\uf0e0", + Linkedin : "\uf0e1", + Undo : "\uf0e2", + Legal : "\uf0e3", + Dashboard : "\uf0e4", + CommentAlt : "\uf0e5", + CommentsAlt : "\uf0e6", + Bolt : "\uf0e7", + Sitemap : "\uf0e8", + Unbrella : "\uf0e9", + Paste : "\uf0ea", + ClosedCaptions : "\uf20a", + UserMd : "\uf200", +}; + diff --git a/playback/player/qt/main.cpp b/playback/player/qt/main.cpp new file mode 100644 index 0000000000..5634732f6f --- /dev/null +++ b/playback/player/qt/main.cpp @@ -0,0 +1,52 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "player.h" + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType("Player", 1, 0, "Player"); + + /* the plugin must be loaded before loading the qml file to register the + * GstGLVideoItem qml item + * FIXME Add a QQmlExtensionPlugin into qmlglsink to register GstGLVideoItem + * with the QML engine, then remove this */ + gst_init(NULL,NULL); + GstElement *sink = gst_element_factory_make ("qmlglsink", NULL); + gst_object_unref(sink); + + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + QObject *rootObject = engine.rootObjects().first(); + + Player *player = rootObject->findChild("player"); + QQuickItem *videoItem = rootObject->findChild("videoItem"); + player->setVideoOutput(videoItem); + + + return app.exec(); +} diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml new file mode 100644 index 0000000000..c7fe42435e --- /dev/null +++ b/playback/player/qt/main.qml @@ -0,0 +1,461 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.4 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Dialogs 1.2 +import QtQuick.Window 2.1 +import Player 1.0 +import org.freedesktop.gstreamer.GLVideoItem 1.0 + +import "fontawesome.js" as FontAwesome + +ApplicationWindow { + id: window + visible: true + width: 640 + height: 480 + x: 30 + y: 30 + color: "black" +// title : player.mediaInfo.title + + Player { + id: player + objectName: "player" + volume: 0.5 + onStateChanged: { + if (state === Player.STOPPED) { + playbutton.text = FontAwesome.Icon.Play + } + } + onResolutionChanged: { + if (player.videoAvailable) { + window.width = resolution.width + window.height = resolution.height + } + } + } + + GstGLVideoItem { + id: video + objectName: "videoItem" + anchors.centerIn: parent + width: 640 + height: 480 + } + + FileDialog { + id: fileDialog + //nameFilters: [TODO globs from mime types] + onAccepted: player.source = fileUrl + } + + Action { + id: fileOpenAction + text: "Open" + onTriggered: fileDialog.open() + } + + menuBar: MenuBar { + Menu { + title: "&File" + MenuItem { action: fileOpenAction } + MenuItem { text: "Quit"; onTriggered: Qt.quit() } + } + } + + Item { + anchors.fill: parent + FontLoader { + source: "fonts/fontawesome-webfont.ttf" + } + + Rectangle { + id : playbar + color: Qt.rgba(1, 1, 1, 0.7) + border.width: 1 + border.color: "white" + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 + anchors.horizontalCenter: parent.horizontalCenter + width : grid.width + 20 + height: 40//childrenRect.height + 20 + radius: 5 + + MouseArea { + id: mousearea + anchors.fill: parent + hoverEnabled: true + onEntered: { + parent.opacity = 1.0 + hidetimer.start() + } + } + + Timer { + id: hidetimer + interval: 10000 + onTriggered: { + parent.opacity = 0.0 + stop() + } + } + + Grid { + id: grid + anchors.horizontalCenter: parent.horizontalCenter +// anchors.top: parent.top +// anchors.topMargin: 5 + + spacing: 7 + rows: 1 + verticalItemAlignment: Qt.AlignVCenter + + Text { + id : openmedia + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.FolderOpen + + MouseArea { + anchors.fill: parent + onPressed: fileDialog.open() + } + } + + Item { + width: 17 + height: 17 + + Text { + anchors.centerIn: parent + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.StepBackward + } + } + + Item { + width: 25 + height: 25 + + Text { + anchors.centerIn: parent + id : playbutton + font.pointSize: 25 + font.family: "FontAwesome" + //font.weight: Font.Light + text: FontAwesome.Icon.PlayCircle + } + + MouseArea { + id: playArea + anchors.fill: parent + onPressed: { + if (player.state !== Player.PLAYING) { + player.play() + playbutton.text = FontAwesome.Icon.Pause + playbutton.font.pointSize = 17 + } else { + player.pause() + playbutton.text = FontAwesome.Icon.PlayCircle + playbutton.font.pointSize = 25 + } + } + } + } + + Item { + width: 17 + height: 17 + + Text { + anchors.centerIn: parent + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.StepForward + } + } + + Item { + width: 40 + height: 17 + Text { + id: timelabel + anchors.centerIn: parent + font.pointSize: 13 + color: "black" + text: { + var current = new Date(Math.floor(slider.value / 1e6)); + current.getMinutes() + ":" + ('0'+current.getSeconds()).slice(-2) + } + } + } + + Item { + width: 200 + height: 38 + Text { + anchors.centerIn: parent + text: player.mediaInfo.title + font.pointSize: 15 + } + } + + + + Item { + width: 40 + height: 17 + Text { + id: durationlabel + anchors.centerIn: parent + font.pointSize: 13 + color: "black" + text: { + var duration = new Date(Math.floor(player.duration / 1e6)); + duration.getMinutes() + ":" + ('0'+duration.getSeconds()).slice(-2) + } + } + } + + Item { + width: 17 + height: 17 + + + Text { + id : volume + anchors.centerIn: parent + font.pointSize: 17 + font.family: "FontAwesome" + text: { + if (volumeslider.value > volumeslider.maximumValue / 2) { + FontAwesome.Icon.VolumeUp + } else if (volumeslider.value === 0) { + FontAwesome.Icon.VolumeOff + } else { + FontAwesome.Icon.VolumeDown + } + } + } + + Rectangle { + id : volumebar + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.top + //anchors.bottomMargin:3 + color: "lightgray" + width: 17 + height: 66 + visible: false + radius: 5 + + Slider { + id: volumeslider + value: player.volume + minimumValue: 0.0 + maximumValue: 1.0 + stepSize: 0.001 + anchors.centerIn: parent + orientation: Qt.Vertical + onPressedChanged: player.volume = value + + style: SliderStyle { + groove: Item { + implicitWidth: 47 + implicitHeight: 3 + anchors.centerIn: parent + + Rectangle { + antialiasing: true + height: parent.height + width: parent.width + color: "gray" + opacity: 0.8 + radius: 5 + + Rectangle { + antialiasing: true + height: parent.height + width: parent.width * control.value / control.maximumValue + color: "red" + radius: 5 + } + } + } + handle: Rectangle { + anchors.centerIn: parent + color: control.pressed ? "white" : "lightgray" + border.color: "gray" + border.width: 1 + implicitWidth: 11 + implicitHeight: 11 + radius: 90 + } + } + } + } + + MouseArea { + anchors.fill: parent + onPressed: { + volumebar.visible = !volumebar.visible + } + } + + MouseArea { + anchors.fill: volumebar + hoverEnabled: true + propagateComposedEvents: true + + onClicked: mouse.accepted = false; + onPressed: mouse.accepted = false; + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPositionChanged: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + + onExited: { + volumebar.visible = false + } + } + + } + + Text { + id: sub + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.ClosedCaptions + } + + Text { + id : fullscreen + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.ResizeFull + + MouseArea { + anchors.fill: parent + onClicked: { + if (window.visibility === Window.FullScreen) { + window.showNormal() + fullscreen.text = FontAwesome.Icon.ResizeFull + } else { + window.showFullScreen() + fullscreen.text = FontAwesome.Icon.ResizeSmall + } + } + } + } + } + + Item { + width: playbar.width + height: 5 + anchors.bottom: playbar.bottom + + Slider { + id: slider + maximumValue: player.duration + value: player.position + onPressedChanged: player.seek(value) + enabled: player.mediaInfo.isSeekable + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + id: sliderMouseArea + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + + onClicked: mouse.accepted = false; + onPressed: mouse.accepted = false; + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPositionChanged: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + } + + Rectangle { + id: hoveredcliptime + width: 40 + height: 17 + color: "lightgray" + anchors.verticalCenter: parent.verticalCenter + visible: sliderMouseArea.containsMouse + x: sliderMouseArea.mouseX + + Text { + font.pointSize: 13 + color: "black" + anchors.centerIn: parent + text: { + var value = (sliderMouseArea.mouseX - slider.x) * player.duration / (slider.width - slider.x) + var date = new Date(Math.floor(value / 1e6)); + date.getMinutes() + ":" + ('0' + date.getSeconds()).slice(-2) + } + } + } + + style: SliderStyle { + groove: Item { + implicitWidth: playbar.width + implicitHeight: 5 + + Rectangle { + height: parent.height + width: parent.width + anchors.verticalCenter: parent.verticalCenter + color: "gray" + opacity: 0.8 + + Rectangle { + antialiasing: true + color: "red" + height: parent.height + width: parent.width * control.value / control.maximumValue + } + + Rectangle { + antialiasing: true + color: "yellow" + height: parent.height + width: parent.width * player.buffering / 100 + } + } + } + handle: Rectangle { + anchors.centerIn: parent + color: control.pressed ? "white" : "lightgray" + implicitWidth: 4 + implicitHeight: 5 + } + } + } + } + + } + } +} diff --git a/playback/player/qt/play.pro b/playback/player/qt/play.pro new file mode 100644 index 0000000000..c8b29549b3 --- /dev/null +++ b/playback/player/qt/play.pro @@ -0,0 +1,53 @@ +TEMPLATE = app + +QT += qml quick widgets + +CONFIG += c++11 + +DEFINES += GST_USE_UNSTABLE_API + +INCLUDEPATH += ../lib + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Default rules for deployment. +include(deployment.pri) + +# not tested (yet) +unix:!macx { +QT_CONFIG -= no-pkg-config +CONFIG += link_pkgconfig +PKGCONFIG = \ + gstreamer-1.0 \ + gstreamer-audio-1.0 \ + gstreamer-tag-1.0 \ + gstreamer-pbutils-1.0 \ + gstreamer-video-1.0 \ + gstreamer-gl-1.0 +} + +macx { + QMAKE_MAC_SDK = macosx10.9 + INCLUDEPATH += /Library/Frameworks/GStreamer.framework/Headers + + LIBS += \ + -framework AppKit \ + -F/Library/Frameworks -framework GStreamer +} + +HEADERS += \ + qgstplayer.h \ + player.h \ + quickrenderer.h + +SOURCES += main.cpp \ + qgstplayer.cpp \ + ../lib/gst/player/gstplayer.c \ + ../lib/gst/player/gstplayer-media-info.c \ + player.cpp \ + quickrenderer.cpp + +DISTFILES += diff --git a/playback/player/qt/player.cpp b/playback/player/qt/player.cpp new file mode 100644 index 0000000000..ec4b78cb28 --- /dev/null +++ b/playback/player/qt/player.cpp @@ -0,0 +1,41 @@ + +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "player.h" +#include "quickrenderer.h" + +Player::Player(QObject *parent) + : Player(parent, new QuickRenderer) +{ + +} + +Player::Player(QObject *parent, QuickRenderer *renderer) + : QGstPlayer(parent, renderer) + , renderer_(renderer) +{ + renderer_->setParent(this); +} + +void Player::setVideoOutput(QQuickItem *output) +{ + renderer_->setVideoItem(output); +} diff --git a/playback/player/qt/player.h b/playback/player/qt/player.h new file mode 100644 index 0000000000..88586f4e03 --- /dev/null +++ b/playback/player/qt/player.h @@ -0,0 +1,44 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include +#include "qgstplayer.h" + +class QuickRenderer; + +class Player : public QGstPlayer +{ + Q_OBJECT +public: + Player(QObject *parent = 0); + void setVideoOutput(QQuickItem *output); + +private: + Player(QObject *parent, QuickRenderer *renderer); + QuickRenderer *renderer_; +}; + +Q_DECLARE_METATYPE(Player*) + +#endif // PLAYER_H diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp new file mode 100644 index 0000000000..c2fb3fbab7 --- /dev/null +++ b/playback/player/qt/qgstplayer.cpp @@ -0,0 +1,435 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "qgstplayer.h" +#include +#include + +#include + +class QGstPlayerRegisterMetaTypes +{ +public: + QGstPlayerRegisterMetaTypes() + { + qRegisterMetaType("State"); + } + +} _register; + +QGstPlayer::MediaInfo::MediaInfo(GstPlayerMediaInfo *media_info) + : mediaInfo_(media_info) +{ + +} + +QString QGstPlayer::MediaInfo::title() const +{ + QString title = QString::fromLocal8Bit + (gst_player_media_info_get_title(mediaInfo_)); + + // if media has no title, return the file name + if (title.isEmpty()) { + QUrl url(gst_player_media_info_get_uri(mediaInfo_)); + title = url.fileName(); + } + + return title; +} + +bool QGstPlayer::MediaInfo::isSeekable() const +{ + return gst_player_media_info_is_seekable(mediaInfo_); +} + +bool QGstPlayer::isVideoAvailable() const +{ + GstPlayerVideoInfo *video_info; + + video_info = gst_player_get_current_video_track (player_); + if (video_info) { + g_object_unref (video_info); + return true; + } + + return false; +} + +QQmlPropertyMap *QGstPlayer::mediaInfo() const +{ + return mediaInfoMap_; +} + +QGstPlayer::QGstPlayer(QObject *parent, QGstPlayer::VideoRenderer *renderer) + : QObject(parent) + , player_() + , state_(STOPPED) + , videoDimensions_(QSize()) + , mediaInfoMap_() + , videoAvailable_(false) +{ + + player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, 0); + + g_object_connect(player_, + "swapped-signal::state-changed", G_CALLBACK (QGstPlayer::onStateChanged), this, + "swapped-signal::position-updated", G_CALLBACK (QGstPlayer::onPositionUpdated), this, + "swapped-signal::duration-changed", G_CALLBACK (QGstPlayer::onDurationChanged), this, + "swapped-signal::buffering", G_CALLBACK (QGstPlayer::onBufferingChanged), this, + "swapped-signal::video-dimensions-changed", G_CALLBACK (QGstPlayer::onVideoDimensionsChanged), this, + "swapped-signal::volume-changed", G_CALLBACK (QGstPlayer::onVolumeChanged), this, + "swapped-signal::mute-changed", G_CALLBACK (QGstPlayer::onMuteChanged), this, + "swapped-signal::media-info-updated", G_CALLBACK (QGstPlayer::onMediaInfoUpdated), this, NULL); + + mediaInfoMap_ = new QQmlPropertyMap(this); +} + +void +QGstPlayer::onStateChanged(QGstPlayer * player, GstPlayerState state) +{ + player->state_ = static_cast(state); + + emit player->stateChanged(player->state_); +} + +void +QGstPlayer::onPositionUpdated(QGstPlayer * player, GstClockTime position) +{ + emit player->positionChanged(position); +} + +void +QGstPlayer::onDurationChanged(QGstPlayer * player, GstClockTime duration) +{ + emit player->durationChanged(duration); +} + +void +QGstPlayer::onBufferingChanged(QGstPlayer * player, int percent) +{ + emit player->bufferingChanged(percent); +} + +void +QGstPlayer::onVideoDimensionsChanged(QGstPlayer * player, int w, int h) +{ + QSize res(w,h); + + player->setResolution(res); + + emit player->resolutionChanged(res); +} + +void +QGstPlayer::onVolumeChanged(QGstPlayer *player) +{ + qreal new_val; + + new_val = gst_player_get_volume (player->player_); + + emit player->volumeChanged(new_val); +} + +void +QGstPlayer::onMuteChanged(QGstPlayer *player) +{ + bool new_val; + + new_val = gst_player_get_mute (player->player_); + + emit player->mutedChanged(new_val); +} + +void +QGstPlayer::onMediaInfoUpdated(QGstPlayer *player, GstPlayerMediaInfo *media_info) +{ + MediaInfo mediaInfo(media_info); + + player->mediaInfoMap_->insert(QLatin1String("title"), + QVariant(mediaInfo.title())); + player->mediaInfoMap_->insert(QLatin1String("isSeekable"), + QVariant(mediaInfo.isSeekable())); + + bool val = player->isVideoAvailable(); + + if (player->videoAvailable_ != val) { + player->videoAvailable_ = val; + emit player->videoAvailableChanged(val); + } + + emit player->mediaInfoChanged(); +} + +QUrl QGstPlayer::source() const +{ + Q_ASSERT(player_ != 0); + QString url = QString::fromLocal8Bit(gst_player_get_uri(player_)); + + return QUrl(url); +} + +qint64 QGstPlayer::duration() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_duration(player_); +} + +qint64 QGstPlayer::position() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_position(player_); +} + +qreal QGstPlayer::volume() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_volume(player_); +} + +bool QGstPlayer::isMuted() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_mute(player_); +} + +int QGstPlayer::buffering() const +{ + return 0; +} + +QSize QGstPlayer::resolution() const +{ + return videoDimensions_; +} + +void QGstPlayer::setResolution(QSize size) +{ + videoDimensions_ = size; +} + +QGstPlayer::State QGstPlayer::state() const +{ + return state_; +} + +GstElement *QGstPlayer::pipeline() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_pipeline(player_); +} + +void QGstPlayer::play() +{ + Q_ASSERT(player_ != 0); + + gst_player_play(player_); +} + +void QGstPlayer::pause() +{ + Q_ASSERT(player_ != 0); + + gst_player_pause(player_); +} + +void QGstPlayer::stop() +{ + Q_ASSERT(player_ != 0); + + gst_player_stop(player_); +} + +void QGstPlayer::seek(qint64 position) +{ + Q_ASSERT(player_ != 0); + + gst_player_seek(player_, position); +} + +void QGstPlayer::setSource(QUrl const& url) +{ + Q_ASSERT(player_ != 0); + QByteArray uri = url.toString().toLocal8Bit(); + + gst_player_set_uri(player_, uri.data()); + + emit sourceChanged(url); +} + +void QGstPlayer::setVolume(qreal val) +{ + Q_ASSERT(player_ != 0); + + gst_player_set_volume(player_, val); +} + +void QGstPlayer::setMuted(bool val) +{ + Q_ASSERT(player_ != 0); + + gst_player_set_mute(player_, val); +} + +void QGstPlayer::setPosition(qint64 pos) +{ + Q_ASSERT(player_ != 0); + + gst_player_seek(player_, pos); +} + +GstPlayerVideoRenderer *QGstPlayer::VideoRenderer::renderer() +{ + return renderer_; +} + +QGstPlayer::VideoRenderer::VideoRenderer() +{ + renderer_ = static_cast + (g_object_new (GST_TYPE_PLAYER_QT_VIDEO_RENDERER, "renderer", this, NULL)); +} + +QGstPlayer::VideoRenderer::~VideoRenderer() +{ + if (renderer_) gst_object_unref(renderer_); +} + +struct _GstPlayerQtVideoRenderer +{ + GObject parent; + + gpointer renderer; +}; + +struct _GstPlayerQtVideoRendererClass +{ + GObjectClass parent_class; +}; + +static void +gst_player_qt_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface); + +G_DEFINE_TYPE_WITH_CODE (GstPlayerQtVideoRenderer, + gst_player_qt_video_renderer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER, + gst_player_qt_video_renderer_interface_init)) + +enum +{ + QT_VIDEO_RENDERER_PROP_0, + QT_VIDEO_RENDERER_PROP_RENDERER, + QT_VIDEO_RENDERER_PROP_LAST +}; + +static GParamSpec * qt_video_renderer_param_specs + [QT_VIDEO_RENDERER_PROP_LAST] = { NULL, }; + +static void +gst_player_qt_video_renderer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); + + switch (prop_id) { + case QT_VIDEO_RENDERER_PROP_RENDERER: + qDebug() << "setting renderer"; + self->renderer = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_qt_video_renderer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); + + switch (prop_id) { + case QT_VIDEO_RENDERER_PROP_RENDERER: + g_value_set_pointer (value, self->renderer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_qt_video_renderer_finalize (GObject * object) +{ + GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); + + G_OBJECT_CLASS + (gst_player_qt_video_renderer_parent_class)->finalize(object); +} + +static void +gst_player_qt_video_renderer_class_init + (GstPlayerQtVideoRendererClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = + gst_player_qt_video_renderer_set_property; + gobject_class->get_property = + gst_player_qt_video_renderer_get_property; + gobject_class->finalize = gst_player_qt_video_renderer_finalize; + + qt_video_renderer_param_specs + [QT_VIDEO_RENDERER_PROP_RENDERER] = + g_param_spec_pointer ("renderer", "Qt Renderer", "", + static_cast(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (gobject_class, + QT_VIDEO_RENDERER_PROP_LAST, + qt_video_renderer_param_specs); +} + +static void +gst_player_qt_video_renderer_init (GstPlayerQtVideoRenderer * self) +{ + +} + +static GstElement * +gst_player_qt_video_renderer_create_video_sink + (GstPlayerVideoRenderer * iface, GstPlayer *player) +{ + GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (iface); + + g_assert(self->renderer != NULL); + + return static_cast(self->renderer)->createVideoSink(); +} + +static void +gst_player_qt_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface) +{ + iface->create_video_sink = gst_player_qt_video_renderer_create_video_sink; +} diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h new file mode 100644 index 0000000000..aeb45a5d35 --- /dev/null +++ b/playback/player/qt/qgstplayer.h @@ -0,0 +1,162 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef QGSTPLAYER_H +#define QGSTPLAYER_H + +#include +#include +#include +//#include +#include +#include + +class QGstPlayer : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) + Q_PROPERTY(qint64 position READ position NOTIFY positionChanged) + Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged) + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) + Q_PROPERTY(int buffering READ buffering NOTIFY bufferingChanged) + Q_PROPERTY(QSize resolution READ resolution WRITE setResolution NOTIFY resolutionChanged) + Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(QObject *mediaInfo READ mediaInfo NOTIFY mediaInfoChanged) + Q_PROPERTY(bool videoAvailable READ isVideoAvailable NOTIFY videoAvailableChanged) + + Q_ENUMS(State) + +public: + + class VideoRenderer; + + explicit QGstPlayer(QObject *parent = 0, VideoRenderer *renderer = 0); + + typedef GstPlayerError Error; + enum State { + STOPPED = GST_PLAYER_STATE_STOPPED, + BUFFERING = GST_PLAYER_STATE_BUFFERING, + PAUSED = GST_PLAYER_STATE_PAUSED, + PLAYING = GST_PLAYER_STATE_PLAYING + }; + + class VideoRenderer + { + public: + GstPlayerVideoRenderer *renderer(); + virtual GstElement *createVideoSink() = 0; + protected: + VideoRenderer(); + virtual ~VideoRenderer(); + private: + GstPlayerVideoRenderer *renderer_; + }; + + // TODO add remaining bits + class MediaInfo + { + public: + MediaInfo(GstPlayerMediaInfo *media_info); + QString title() const; + bool isSeekable() const; + private: + GstPlayerMediaInfo *mediaInfo_; + }; + + QUrl source() const; + qint64 duration() const; + qint64 position() const; + qreal volume() const; + bool isMuted() const; + int buffering() const; + State state() const; + GstElement *pipeline() const; + QSize resolution() const; + void setResolution(QSize size); + bool isVideoAvailable() const; + QQmlPropertyMap *mediaInfo() const; + + +signals: + void stateChanged(State new_state); + void bufferingChanged(int percent); + void enfOfStream(); + void positionChanged(qint64 new_position); + void durationChanged(qint64 duration); + void resolutionChanged(QSize resolution); + void volumeChanged(qreal volume); + void mutedChanged(bool muted); + void mediaInfoChanged(); + void sourceChanged(QUrl new_url); + void videoAvailableChanged(bool videoAvailable); + +public slots: + void play(); + void pause(); + void stop(); + void seek(qint64 position); + void setSource(QUrl const& url); + void setVolume(qreal val); + void setMuted(bool val); + void setPosition(qint64 pos); + +private: + Q_DISABLE_COPY(QGstPlayer) + static void onStateChanged(QGstPlayer *, GstPlayerState state); + static void onPositionUpdated(QGstPlayer *, GstClockTime position); + static void onDurationChanged(QGstPlayer *, GstClockTime duration); + static void onBufferingChanged(QGstPlayer *, int percent); + static void onVideoDimensionsChanged(QGstPlayer *, int w, int h); + static void onVolumeChanged(QGstPlayer *); + static void onMuteChanged(QGstPlayer *); + static void onMediaInfoUpdated(QGstPlayer *, GstPlayerMediaInfo *media_info); + + GstPlayer *player_; + State state_; + QSize videoDimensions_; + QQmlPropertyMap *mediaInfoMap_; + bool videoAvailable_; +}; + +G_BEGIN_DECLS + +typedef struct _GstPlayerQtVideoRenderer + GstPlayerQtVideoRenderer; + +typedef struct _GstPlayerQtVideoRendererClass + GstPlayerQtVideoRendererClass; + +#define GST_TYPE_PLAYER_QT_VIDEO_RENDERER (gst_player_qt_video_renderer_get_type ()) +#define GST_IS_PLAYER_QT_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_QT_VIDEO_RENDERER)) +#define GST_IS_PLAYER_QT_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_QT_VIDEO_RENDERER)) +#define GST_PLAYER_QT_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_QT_VIDEO_RENDERER, GstPlayerQtVideoRendererClass)) +#define GST_PLAYER_QT_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_QT_VIDEO_RENDERER, GstPlayerQtVideoRenderer)) +#define GST_PLAYER_QT_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_QT_VIDEO_RENDERER, GstPlayerQtVideoRendererClass)) +#define GST_PLAYER_QT_VIDEO_RENDERER_CAST(obj) ((GstPlayerQtVideoRenderer*)(obj)) + +GType gst_player_qt_video_renderer_get_type (void); + +G_END_DECLS + +Q_DECLARE_METATYPE(QGstPlayer*) +Q_DECLARE_METATYPE(QGstPlayer::State) + +#endif // QGSTPLAYER_H diff --git a/playback/player/qt/qml.qrc b/playback/player/qt/qml.qrc new file mode 100644 index 0000000000..105221780b --- /dev/null +++ b/playback/player/qt/qml.qrc @@ -0,0 +1,9 @@ + + + main.qml + fontawesome.js + + + fontawesome-webfont.ttf + + diff --git a/playback/player/qt/quickrenderer.cpp b/playback/player/qt/quickrenderer.cpp new file mode 100644 index 0000000000..5dd68533ec --- /dev/null +++ b/playback/player/qt/quickrenderer.cpp @@ -0,0 +1,56 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "quickrenderer.h" + +QuickRenderer::QuickRenderer(QObject *parent) + : QObject(parent) + , QGstPlayer::VideoRenderer() + , sink() +{ + +} + +QuickRenderer::~QuickRenderer() +{ + if (sink) gst_object_unref(sink); +} + +GstElement *QuickRenderer::createVideoSink() +{ + GstElement *qmlglsink = gst_element_factory_make("qmlglsink", NULL); + + GstElement *glsinkbin = gst_element_factory_make ("glsinkbin", NULL); + + Q_ASSERT(qmlglsink && glsinkbin); + + g_object_set (glsinkbin, "sink", qmlglsink, NULL); + + sink = static_cast(gst_object_ref_sink(qmlglsink)); + + return glsinkbin; +} + +void QuickRenderer::setVideoItem(QQuickItem *item) +{ + Q_ASSERT(item); + + g_object_set(sink, "widget", item, NULL); +} diff --git a/playback/player/qt/quickrenderer.h b/playback/player/qt/quickrenderer.h new file mode 100644 index 0000000000..99519fbd8b --- /dev/null +++ b/playback/player/qt/quickrenderer.h @@ -0,0 +1,42 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef QUICKPLAYER_H +#define QUICKPLAYER_H + +#include +#include +#include "qgstplayer.h" + +class QuickRenderer : public QObject, public QGstPlayer::VideoRenderer +{ + Q_OBJECT +public: + QuickRenderer(QObject *parent = 0); + ~QuickRenderer(); + + GstElement *createVideoSink(); + void setVideoItem(QQuickItem *item); + +private: + GstElement *sink; +}; + +#endif // QUICKPLAYER_H From 2ea1c9aee74a6ce994279cb81e443675150959c8 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Tue, 6 Oct 2015 23:19:03 +0800 Subject: [PATCH 132/412] playback/player: qt: emit player signals from Qt event loop --- playback/player/qt/qgstplayer.cpp | 144 +++++++++++++++++++++++++++++- playback/player/qt/qgstplayer.h | 27 +++++- 2 files changed, 164 insertions(+), 7 deletions(-) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index c2fb3fbab7..c2c0ee0c9d 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -21,8 +21,9 @@ #include "qgstplayer.h" #include #include - #include +#include +#include class QGstPlayerRegisterMetaTypes { @@ -86,7 +87,8 @@ QGstPlayer::QGstPlayer(QObject *parent, QGstPlayer::VideoRenderer *renderer) , videoAvailable_(false) { - player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, 0); + player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, + gst_player_qt_signal_dispatcher_new(this)); g_object_connect(player_, "swapped-signal::state-changed", G_CALLBACK (QGstPlayer::onStateChanged), this, @@ -354,7 +356,6 @@ gst_player_qt_video_renderer_set_property (GObject * object, switch (prop_id) { case QT_VIDEO_RENDERER_PROP_RENDERER: - qDebug() << "setting renderer"; self->renderer = g_value_get_pointer (value); break; default: @@ -433,3 +434,140 @@ gst_player_qt_video_renderer_interface_init { iface->create_video_sink = gst_player_qt_video_renderer_create_video_sink; } + +struct _GstPlayerQtSignalDispatcher +{ + GObject parent; + + gpointer player; +}; + +struct _GstPlayerQtSignalDispatcherClass +{ + GObjectClass parent_class; +}; + +static void +gst_player_qt_signal_dispatcher_interface_init + (GstPlayerSignalDispatcherInterface * iface); + +enum +{ + QT_SIGNAL_DISPATCHER_PROP_0, + QT_SIGNAL_DISPATCHER_PROP_PLAYER, + QT_SIGNAL_DISPATCHER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstPlayerQtSignalDispatcher, + gst_player_qt_signal_dispatcher, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_SIGNAL_DISPATCHER, + gst_player_qt_signal_dispatcher_interface_init)); + +static GParamSpec +* qt_signal_dispatcher_param_specs +[QT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; + +static void +gst_player_qt_signal_dispatcher_finalize (GObject * object) +{ + GstPlayerQtSignalDispatcher *self = + GST_PLAYER_QT_SIGNAL_DISPATCHER (object); + + G_OBJECT_CLASS + (gst_player_qt_signal_dispatcher_parent_class)->finalize + (object); +} + +static void +gst_player_qt_signal_dispatcher_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPlayerQtSignalDispatcher *self = + GST_PLAYER_QT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case QT_SIGNAL_DISPATCHER_PROP_PLAYER: + self->player = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_qt_signal_dispatcher_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPlayerQtSignalDispatcher *self = + GST_PLAYER_QT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case QT_SIGNAL_DISPATCHER_PROP_PLAYER: + g_value_set_pointer (value, self->player); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void + gst_player_qt_signal_dispatcher_class_init + (GstPlayerQtSignalDispatcherClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = + gst_player_qt_signal_dispatcher_finalize; + gobject_class->set_property = + gst_player_qt_signal_dispatcher_set_property; + gobject_class->get_property = + gst_player_qt_signal_dispatcher_get_property; + + qt_signal_dispatcher_param_specs + [QT_SIGNAL_DISPATCHER_PROP_PLAYER] = + g_param_spec_pointer ("player", "QGstPlayer instance", "", + static_cast(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (gobject_class, + QT_SIGNAL_DISPATCHER_PROP_LAST, + qt_signal_dispatcher_param_specs); +} + +static void +gst_player_qt_signal_dispatcher_init + (GstPlayerQtSignalDispatcher * self) +{ + +} + +static void +gst_player_qt_signal_dispatcher_dispatch (GstPlayerSignalDispatcher + * iface, GstPlayer * player, void (*emitter) (gpointer data), gpointer data, + GDestroyNotify destroy) +{ + GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (iface); + QObject dispatch; + QObject *receiver = static_cast(self->player); + + QObject::connect(&dispatch, &QObject::destroyed, receiver, [=]() { + emitter(data); + if (destroy) destroy(data); + }, Qt::QueuedConnection); +} + +static void +gst_player_qt_signal_dispatcher_interface_init +(GstPlayerSignalDispatcherInterface * iface) +{ + iface->dispatch = gst_player_qt_signal_dispatcher_dispatch; +} + +GstPlayerSignalDispatcher * +gst_player_qt_signal_dispatcher_new (gpointer player) +{ + return static_cast + (g_object_new (GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER, + "player", player, NULL)); +} diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index aeb45a5d35..c0c39d0d91 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -136,7 +136,10 @@ private: bool videoAvailable_; }; -G_BEGIN_DECLS +Q_DECLARE_METATYPE(QGstPlayer*) +Q_DECLARE_METATYPE(QGstPlayer::State) + +extern "C" { typedef struct _GstPlayerQtVideoRenderer GstPlayerQtVideoRenderer; @@ -154,9 +157,25 @@ typedef struct _GstPlayerQtVideoRendererClass GType gst_player_qt_video_renderer_get_type (void); -G_END_DECLS +typedef struct _GstPlayerQtSignalDispatcher + GstPlayerQtSignalDispatcher; -Q_DECLARE_METATYPE(QGstPlayer*) -Q_DECLARE_METATYPE(QGstPlayer::State) +typedef struct _GstPlayerQtSignalDispatcherClass + GstPlayerQtSignalDispatcherClass; + +#define GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER (gst_player_qt_signal_dispatcher_get_type ()) +#define GST_IS_PLAYER_QT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER)) +#define GST_IS_PLAYER_QT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER)) +#define GST_PLAYER_QT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER, GstPlayerQtSignalDispatcherClass)) +#define GST_PLAYER_QT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER, GstPlayerQtSignalDispatcher)) +#define GST_PLAYER_QT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER, GstPlayerQtSignalDispatcherClass)) +#define GST_PLAYER_QT_SIGNAL_DISPATCHER_CAST(obj) ((GstPlayerQtSignalDispatcher*)(obj)) + +GType gst_player_qt_video_renderer_get_type (void); + +GstPlayerSignalDispatcher * +gst_player_qt_signal_dispatcher_new (gpointer player); + +} #endif // QGSTPLAYER_H From c8d7277d70378affb9096544f3b3edaa87fcf2c3 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Thu, 22 Oct 2015 23:05:35 +0800 Subject: [PATCH 133/412] playback/player: qt: add stream selection support --- playback/player/qt/main.qml | 294 +++++++++++++++++++++-- playback/player/qt/player.cpp | 2 +- playback/player/qt/player.h | 2 +- playback/player/qt/qgstplayer.cpp | 377 ++++++++++++++++++++++++------ playback/player/qt/qgstplayer.h | 193 +++++++++++---- 5 files changed, 739 insertions(+), 129 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index c7fe42435e..9825026f2e 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -44,7 +44,7 @@ ApplicationWindow { volume: 0.5 onStateChanged: { if (state === Player.STOPPED) { - playbutton.text = FontAwesome.Icon.Play + playbutton.state = "play" } } onResolutionChanged: { @@ -116,16 +116,236 @@ ApplicationWindow { interval: 10000 onTriggered: { parent.opacity = 0.0 + settings.visible = false stop() } } + Rectangle { + id: settings + width: 150; height: settingsView.contentHeight + color: Qt.rgba(1, 1, 1, 0.7) + anchors.right: parent.right + anchors.bottom: parent.top + anchors.bottomMargin: 3 + border.width: 1 + border.color: "white" + radius: 5 + visible: false + + ListModel { + id: settingsModel + ListElement { + name: "Video" + } + ListElement { + name: "Audio" + } + ListElement { + name: "Subtitle" + } + } + + Component { + id: settingsDelegate + Item { + width: 150; height: 20 + Text { + text: model.name + font.pointSize: 13 + anchors.centerIn: parent + } + + Text { + font.pointSize: 13 + font.family: "FontAwesome" + text: FontAwesome.Icon.ChevronRight + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + } + + MouseArea { + anchors.fill: parent + onClicked: { + switch(name) { + case 'Video': + videos.visible = true + break + case 'Audio': + audios.visible = true + break + case 'Subtitle' : + subtitles.visible = true + break + } + settings.visible = false + } + } + } + } + + ListView { + id: settingsView + anchors.fill: parent + model: settingsModel + delegate: settingsDelegate + } + } + + Rectangle { + id: videos + width: 150; height: videoView.contentHeight + color: Qt.rgba(1, 1, 1, 0.7) + anchors.right: parent.right + anchors.bottom: parent.top + anchors.bottomMargin: 3 + border.width: 1 + border.color: "white" + radius: 5 + + property bool selected: ListView.isCurrentItem + visible: false + + Component { + id: videoDelegate + Item { + width: 150; height: 20 + Text { + text: model.modelData.resolution.width + 'x' + model.modelData.resolution.height + font.pointSize: 13 + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + parent.ListView.view.currentIndex = index + player.currentVideo = model.modelData + } + } + } + } + + ListView { + id : videoView + anchors.fill: parent + model: player.mediaInfo.videoStreams + delegate: videoDelegate + highlight: Rectangle { + color: "white" + radius: 5 + border.width: 1 + border.color: "white" + } + focus: true + clip: true + } + } + + Rectangle { + id: audios + width: 150; height: audioView.contentHeight + color: Qt.rgba(1, 1, 1, 0.7) + anchors.right: parent.right + anchors.bottom: parent.top + anchors.bottomMargin: 3 + border.width: 1 + border.color: "white" + radius: 5 + + property bool selected: ListView.isCurrentItem + visible: false + + Component { + id: audioDelegate + Item { + width: 150; height: 20 + Text { + text: model.modelData.channels + 'channels' + font.pointSize: 13 + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + parent.ListView.view.currentIndex = index + player.currentAudio = model.modelData + } + } + } + } + + ListView { + id : audioView + anchors.fill: parent + model: player.mediaInfo.audioStreams + delegate: audioDelegate + highlight: Rectangle { + color: "white" + radius: 5 + border.width: 1 + border.color: "white" + } + focus: true + clip: true + } + } + + Rectangle { + id: subtitles + width: 150; height: subtitleView.contentHeight + color: Qt.rgba(1, 1, 1, 0.7) + anchors.right: parent.right + anchors.bottom: parent.top + anchors.bottomMargin: 3 + border.width: 1 + border.color: "white" + radius: 5 + + property bool selected: ListView.isCurrentItem + visible: false + + Component { + id: subtitleDelegate + Item { + width: 150; height: 20 + Text { + text: model.modelData.language + font.pointSize: 13 + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + parent.ListView.view.currentIndex = index + player.currentSubtitle = model.modelData + } + } + } + } + + ListView { + id : subtitleView + anchors.fill: parent + model: player.mediaInfo.subtitleStreams + delegate: subtitleDelegate + highlight: Rectangle { + color: "white" + radius: 5 + border.width: 1 + border.color: "white" + } + focus: true + clip: true + } + } + Grid { id: grid anchors.horizontalCenter: parent.horizontalCenter -// anchors.top: parent.top -// anchors.topMargin: 5 - spacing: 7 rows: 1 verticalItemAlignment: Qt.AlignVCenter @@ -161,10 +381,27 @@ ApplicationWindow { Text { anchors.centerIn: parent id : playbutton - font.pointSize: 25 font.family: "FontAwesome" - //font.weight: Font.Light - text: FontAwesome.Icon.PlayCircle + state: "play" + + states: [ + State { + name: "play" + PropertyChanges { + target: playbutton + text: FontAwesome.Icon.PlayCircle + font.pointSize: 25 + } + }, + State { + name: "pause" + PropertyChanges { + target: playbutton + text: FontAwesome.Icon.Pause + font.pointSize: 17 + } + } + ] } MouseArea { @@ -173,12 +410,10 @@ ApplicationWindow { onPressed: { if (player.state !== Player.PLAYING) { player.play() - playbutton.text = FontAwesome.Icon.Pause - playbutton.font.pointSize = 17 + playbutton.state = "pause" } else { player.pause() - playbutton.text = FontAwesome.Icon.PlayCircle - playbutton.font.pointSize = 25 + playbutton.state = "play" } } } @@ -221,8 +456,6 @@ ApplicationWindow { } } - - Item { width: 40 height: 17 @@ -238,11 +471,25 @@ ApplicationWindow { } } + Text { + id: sub + font.pointSize: 17 + font.family: "FontAwesome" + text: FontAwesome.Icon.ClosedCaptions + color: player.subtitleEnabled ? "red" : "black" + + MouseArea { + anchors.fill: parent + onClicked: { + player.subtitleEnabled = !player.subtitleEnabled + } + } + } + Item { width: 17 height: 17 - Text { id : volume anchors.centerIn: parent @@ -343,10 +590,22 @@ ApplicationWindow { } Text { - id: sub + id: cog font.pointSize: 17 font.family: "FontAwesome" - text: FontAwesome.Icon.ClosedCaptions + text: FontAwesome.Icon.Cog + + MouseArea { + anchors.fill: parent + onClicked: { + settings.visible = !settings.visible + videos.visible = false + audios.visible = false + subtitles.visible = false + + + } + } } Text { @@ -380,7 +639,7 @@ ApplicationWindow { maximumValue: player.duration value: player.position onPressedChanged: player.seek(value) - enabled: player.mediaInfo.isSeekable + enabled: player.mediaInfo.seekable anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter @@ -455,7 +714,6 @@ ApplicationWindow { } } } - } } } diff --git a/playback/player/qt/player.cpp b/playback/player/qt/player.cpp index ec4b78cb28..3e2cade32f 100644 --- a/playback/player/qt/player.cpp +++ b/playback/player/qt/player.cpp @@ -29,7 +29,7 @@ Player::Player(QObject *parent) } Player::Player(QObject *parent, QuickRenderer *renderer) - : QGstPlayer(parent, renderer) + : QGstPlayer::Player(parent, renderer) , renderer_(renderer) { renderer_->setParent(this); diff --git a/playback/player/qt/player.h b/playback/player/qt/player.h index 88586f4e03..34a00028f9 100644 --- a/playback/player/qt/player.h +++ b/playback/player/qt/player.h @@ -27,7 +27,7 @@ class QuickRenderer; -class Player : public QGstPlayer +class Player : public QGstPlayer::Player { Q_OBJECT public: diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index c2c0ee0c9d..e6fc476a34 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -24,43 +24,200 @@ #include #include #include +#include -class QGstPlayerRegisterMetaTypes +namespace QGstPlayer { + +class RegisterMetaTypes { public: - QGstPlayerRegisterMetaTypes() + RegisterMetaTypes() { - qRegisterMetaType("State"); + qRegisterMetaType("State"); } } _register; -QGstPlayer::MediaInfo::MediaInfo(GstPlayerMediaInfo *media_info) - : mediaInfo_(media_info) +MediaInfo::MediaInfo(Player *player) + : QObject(player) + , uri_() + , title_() + , isSeekable_(false) { } -QString QGstPlayer::MediaInfo::title() const +QString MediaInfo::uri() const { - QString title = QString::fromLocal8Bit - (gst_player_media_info_get_title(mediaInfo_)); + return uri_; +} - // if media has no title, return the file name - if (title.isEmpty()) { - QUrl url(gst_player_media_info_get_uri(mediaInfo_)); - title = url.fileName(); +QString MediaInfo::title() const +{ + return title_; +} + +bool MediaInfo::isSeekable() const +{ + return isSeekable_; +} + +const QList &MediaInfo::videoStreams() const +{ + return videoStreams_; +} + +const QList &MediaInfo::audioStreams() const +{ + return audioStreams_; +} + +const QList &MediaInfo::subtitleStreams() const +{ + return subtitleStreams_; +} + +void MediaInfo::update(GstPlayerMediaInfo *info) +{ + Q_ASSERT(info != 0); + + // FIXME since media-info signal gets emitted + // several times, we just update info iff the + // media uri has changed. + if (uri_ == gst_player_media_info_get_uri(info)) { + return; } - return title; + uri_ = QString(gst_player_media_info_get_uri(info)); + + title_ = QString::fromLocal8Bit(gst_player_media_info_get_title(info)); + + // if media has no title, return the file name + if (title_.isEmpty()) { + QUrl url(gst_player_media_info_get_uri(info)); + title_ = url.fileName(); + } + + emit titleChanged(); + + if (isSeekable_ != gst_player_media_info_is_seekable(info)) { + isSeekable_ = !isSeekable_; + emit seekableChanged(); + } + + if (!subtitleStreams_.isEmpty()) { + qDeleteAll(subtitleStreams_); + subtitleStreams_.clear(); + } + + if (!videoStreams_.isEmpty()) { + qDeleteAll(videoStreams_); + videoStreams_.clear(); + } + + if (!audioStreams_.isEmpty()) { + qDeleteAll(audioStreams_); + audioStreams_.clear(); + } + + GList *list = gst_player_get_subtitle_streams(info); + + g_list_foreach (list, [](gpointer data, gpointer user_data) { + GstPlayerSubtitleInfo *info = static_cast(data); + QList *subs = static_cast*>(user_data); + + subs->append(new SubtitleInfo(info)); + }, &subtitleStreams_); + + list = gst_player_get_video_streams(info); + + g_list_foreach (list, [](gpointer data, gpointer user_data) { + GstPlayerVideoInfo *info = static_cast(data); + QList *videos = static_cast*>(user_data); + + videos->append(new VideoInfo(info)); + }, &videoStreams_); + + list = gst_player_get_audio_streams(info); + + g_list_foreach (list, [](gpointer data, gpointer user_data) { + GstPlayerAudioInfo *info = static_cast(data); + QList *audios = static_cast*>(user_data); + + audios->append(new AudioInfo(info)); + }, &audioStreams_); } -bool QGstPlayer::MediaInfo::isSeekable() const +VideoInfo::VideoInfo(GstPlayerVideoInfo *info) + : StreamInfo(reinterpret_cast(info)) + , video_(info) + , resolution_(gst_player_video_info_get_width(info), gst_player_video_info_get_height(info)) { - return gst_player_media_info_is_seekable(mediaInfo_); + } -bool QGstPlayer::isVideoAvailable() const +QSize VideoInfo::resolution() const +{ + return resolution_; +} + +AudioInfo::AudioInfo(GstPlayerAudioInfo *info) + : StreamInfo(reinterpret_cast(info)) + , audio_(info) + , language_(gst_player_audio_info_get_language(info)) + , channels_(gst_player_audio_info_get_channels(info)) + , bitRate_(gst_player_audio_info_get_bitrate(info)) + , sampleRate_(gst_player_audio_info_get_sample_rate(info)) +{ + +} + +const QString &AudioInfo::language() const +{ + return language_; +} + +int AudioInfo::channels() const +{ + return channels_; +} + +int AudioInfo::bitRate() const +{ + return bitRate_; +} + +int AudioInfo::sampleRate() const +{ + return sampleRate_; +} + +SubtitleInfo::SubtitleInfo(GstPlayerSubtitleInfo *info) + : StreamInfo(reinterpret_cast(info)) + , subtitle_(info) + , language_(gst_player_subtitle_info_get_language(info)) +{ + +} + +const QString &SubtitleInfo::language() const +{ + return language_; +} + +int StreamInfo::index() const +{ + return index_; +} + +StreamInfo::StreamInfo(GstPlayerStreamInfo *info) + : stream_(info) + , index_(gst_player_stream_info_get_index(info)) +{ + +} + +bool Player::isVideoAvailable() const { GstPlayerVideoInfo *video_info; @@ -73,74 +230,163 @@ bool QGstPlayer::isVideoAvailable() const return false; } -QQmlPropertyMap *QGstPlayer::mediaInfo() const +MediaInfo *Player::mediaInfo() const { - return mediaInfoMap_; + return mediaInfo_; } -QGstPlayer::QGstPlayer(QObject *parent, QGstPlayer::VideoRenderer *renderer) +QVariant Player::currentVideo() const +{ + Q_ASSERT(player_ != 0); + + GstPlayerVideoInfo *track = gst_player_get_current_video_track(player_); + + if (!track) + return QVariant(); + + return QVariant::fromValue(new VideoInfo(track)); +} + +QVariant Player::currentAudio() const +{ + Q_ASSERT(player_ != 0); + + GstPlayerAudioInfo *track = gst_player_get_current_audio_track(player_); + + if (!track) + return QVariant(); + + return QVariant::fromValue(new AudioInfo(track)); +} + +QVariant Player::currentSubtitle() const +{ + Q_ASSERT(player_ != 0); + + GstPlayerSubtitleInfo *track = gst_player_get_current_subtitle_track(player_); + + if (!track) + return QVariant(); + + return QVariant::fromValue(new SubtitleInfo(track)); +} + +void Player::setCurrentVideo(QVariant track) +{ + Q_ASSERT(player_ != 0); + + VideoInfo* info = track.value(); + Q_ASSERT(info); + + gst_player_set_video_track(player_, info->index()); +} + +void Player::setCurrentAudio(QVariant track) +{ + Q_ASSERT(player_ != 0); + + AudioInfo* info = track.value(); + Q_ASSERT(info); + + gst_player_set_audio_track(player_, info->index()); + +} + +void Player::setCurrentSubtitle(QVariant track) +{ + Q_ASSERT(player_ != 0); + + SubtitleInfo* info = track.value(); + Q_ASSERT(info); + + gst_player_set_subtitle_track(player_, info->index()); +} + +bool Player::isSubtitleEnabled() const +{ + return subtitleEnabled_; +} + +void Player::setSubtitleEnabled(bool enabled) +{ + Q_ASSERT(player_ != 0); + + subtitleEnabled_ = enabled; + + gst_player_set_subtitle_track_enabled(player_, enabled); + + emit subtitleEnabledChanged(enabled); +} + +Player::Player(QObject *parent, VideoRenderer *renderer) : QObject(parent) , player_() , state_(STOPPED) , videoDimensions_(QSize()) - , mediaInfoMap_() + , mediaInfo_() , videoAvailable_(false) + , subtitleEnabled_(false) { player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, gst_player_qt_signal_dispatcher_new(this)); g_object_connect(player_, - "swapped-signal::state-changed", G_CALLBACK (QGstPlayer::onStateChanged), this, - "swapped-signal::position-updated", G_CALLBACK (QGstPlayer::onPositionUpdated), this, - "swapped-signal::duration-changed", G_CALLBACK (QGstPlayer::onDurationChanged), this, - "swapped-signal::buffering", G_CALLBACK (QGstPlayer::onBufferingChanged), this, - "swapped-signal::video-dimensions-changed", G_CALLBACK (QGstPlayer::onVideoDimensionsChanged), this, - "swapped-signal::volume-changed", G_CALLBACK (QGstPlayer::onVolumeChanged), this, - "swapped-signal::mute-changed", G_CALLBACK (QGstPlayer::onMuteChanged), this, - "swapped-signal::media-info-updated", G_CALLBACK (QGstPlayer::onMediaInfoUpdated), this, NULL); + "swapped-signal::state-changed", G_CALLBACK (Player::onStateChanged), this, + "swapped-signal::position-updated", G_CALLBACK (Player::onPositionUpdated), this, + "swapped-signal::duration-changed", G_CALLBACK (Player::onDurationChanged), this, + "swapped-signal::buffering", G_CALLBACK (Player::onBufferingChanged), this, + "swapped-signal::video-dimensions-changed", G_CALLBACK (Player::onVideoDimensionsChanged), this, + "swapped-signal::volume-changed", G_CALLBACK (Player::onVolumeChanged), this, + "swapped-signal::mute-changed", G_CALLBACK (Player::onMuteChanged), this, + "swapped-signal::media-info-updated", G_CALLBACK (Player::onMediaInfoUpdated), this, NULL); - mediaInfoMap_ = new QQmlPropertyMap(this); + mediaInfo_ = new MediaInfo(this); + gst_player_set_subtitle_track_enabled(player_, false); } void -QGstPlayer::onStateChanged(QGstPlayer * player, GstPlayerState state) +Player::onStateChanged(Player * player, GstPlayerState state) { - player->state_ = static_cast(state); + player->state_ = static_cast(state); emit player->stateChanged(player->state_); } void -QGstPlayer::onPositionUpdated(QGstPlayer * player, GstClockTime position) +Player::onPositionUpdated(Player * player, GstClockTime position) { emit player->positionChanged(position); } void -QGstPlayer::onDurationChanged(QGstPlayer * player, GstClockTime duration) +Player::onDurationChanged(Player * player, GstClockTime duration) { emit player->durationChanged(duration); } void -QGstPlayer::onBufferingChanged(QGstPlayer * player, int percent) +Player::onBufferingChanged(Player * player, int percent) { emit player->bufferingChanged(percent); } void -QGstPlayer::onVideoDimensionsChanged(QGstPlayer * player, int w, int h) +Player::onVideoDimensionsChanged(Player * player, int w, int h) { QSize res(w,h); + if (res == player->videoDimensions_) + return; + player->videoDimensions_ = res; + player->setResolution(res); emit player->resolutionChanged(res); } void -QGstPlayer::onVolumeChanged(QGstPlayer *player) +Player::onVolumeChanged(Player *player) { qreal new_val; @@ -150,7 +396,7 @@ QGstPlayer::onVolumeChanged(QGstPlayer *player) } void -QGstPlayer::onMuteChanged(QGstPlayer *player) +Player::onMuteChanged(Player *player) { bool new_val; @@ -160,15 +406,8 @@ QGstPlayer::onMuteChanged(QGstPlayer *player) } void -QGstPlayer::onMediaInfoUpdated(QGstPlayer *player, GstPlayerMediaInfo *media_info) +Player::onMediaInfoUpdated(Player *player, GstPlayerMediaInfo *media_info) { - MediaInfo mediaInfo(media_info); - - player->mediaInfoMap_->insert(QLatin1String("title"), - QVariant(mediaInfo.title())); - player->mediaInfoMap_->insert(QLatin1String("isSeekable"), - QVariant(mediaInfo.isSeekable())); - bool val = player->isVideoAvailable(); if (player->videoAvailable_ != val) { @@ -176,10 +415,12 @@ QGstPlayer::onMediaInfoUpdated(QGstPlayer *player, GstPlayerMediaInfo *media_inf emit player->videoAvailableChanged(val); } + player->mediaInfo()->update(media_info); + emit player->mediaInfoChanged(); } -QUrl QGstPlayer::source() const +QUrl Player::source() const { Q_ASSERT(player_ != 0); QString url = QString::fromLocal8Bit(gst_player_get_uri(player_)); @@ -187,90 +428,90 @@ QUrl QGstPlayer::source() const return QUrl(url); } -qint64 QGstPlayer::duration() const +qint64 Player::duration() const { Q_ASSERT(player_ != 0); return gst_player_get_duration(player_); } -qint64 QGstPlayer::position() const +qint64 Player::position() const { Q_ASSERT(player_ != 0); return gst_player_get_position(player_); } -qreal QGstPlayer::volume() const +qreal Player::volume() const { Q_ASSERT(player_ != 0); return gst_player_get_volume(player_); } -bool QGstPlayer::isMuted() const +bool Player::isMuted() const { Q_ASSERT(player_ != 0); return gst_player_get_mute(player_); } -int QGstPlayer::buffering() const +int Player::buffering() const { return 0; } -QSize QGstPlayer::resolution() const +QSize Player::resolution() const { return videoDimensions_; } -void QGstPlayer::setResolution(QSize size) +void Player::setResolution(QSize size) { videoDimensions_ = size; } -QGstPlayer::State QGstPlayer::state() const +Player::State Player::state() const { return state_; } -GstElement *QGstPlayer::pipeline() const +GstElement *Player::pipeline() const { Q_ASSERT(player_ != 0); return gst_player_get_pipeline(player_); } -void QGstPlayer::play() +void Player::play() { Q_ASSERT(player_ != 0); gst_player_play(player_); } -void QGstPlayer::pause() +void Player::pause() { Q_ASSERT(player_ != 0); gst_player_pause(player_); } -void QGstPlayer::stop() +void Player::stop() { Q_ASSERT(player_ != 0); gst_player_stop(player_); } -void QGstPlayer::seek(qint64 position) +void Player::seek(qint64 position) { Q_ASSERT(player_ != 0); gst_player_seek(player_, position); } -void QGstPlayer::setSource(QUrl const& url) +void Player::setSource(QUrl const& url) { Q_ASSERT(player_ != 0); QByteArray uri = url.toString().toLocal8Bit(); @@ -280,43 +521,45 @@ void QGstPlayer::setSource(QUrl const& url) emit sourceChanged(url); } -void QGstPlayer::setVolume(qreal val) +void Player::setVolume(qreal val) { Q_ASSERT(player_ != 0); gst_player_set_volume(player_, val); } -void QGstPlayer::setMuted(bool val) +void Player::setMuted(bool val) { Q_ASSERT(player_ != 0); gst_player_set_mute(player_, val); } -void QGstPlayer::setPosition(qint64 pos) +void Player::setPosition(qint64 pos) { Q_ASSERT(player_ != 0); gst_player_seek(player_, pos); } -GstPlayerVideoRenderer *QGstPlayer::VideoRenderer::renderer() +GstPlayerVideoRenderer *VideoRenderer::renderer() { return renderer_; } -QGstPlayer::VideoRenderer::VideoRenderer() +VideoRenderer::VideoRenderer() { renderer_ = static_cast (g_object_new (GST_TYPE_PLAYER_QT_VIDEO_RENDERER, "renderer", this, NULL)); } -QGstPlayer::VideoRenderer::~VideoRenderer() +VideoRenderer::~VideoRenderer() { if (renderer_) gst_object_unref(renderer_); } +} + struct _GstPlayerQtVideoRenderer { GObject parent; @@ -527,7 +770,7 @@ static void qt_signal_dispatcher_param_specs [QT_SIGNAL_DISPATCHER_PROP_PLAYER] = - g_param_spec_pointer ("player", "QGstPlayer instance", "", + g_param_spec_pointer ("player", "Player instance", "", static_cast(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (gobject_class, diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index c0c39d0d91..1f4dc6f00d 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -28,7 +28,16 @@ #include #include -class QGstPlayer : public QObject +namespace QGstPlayer { + +class VideoRenderer; +class MediaInfo; +class StreamInfo; +class VideInfo; +class AudioInfo; +class SubtitleInfo; + +class Player : public QObject { Q_OBJECT Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) @@ -41,14 +50,16 @@ class QGstPlayer : public QObject Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(QObject *mediaInfo READ mediaInfo NOTIFY mediaInfoChanged) Q_PROPERTY(bool videoAvailable READ isVideoAvailable NOTIFY videoAvailableChanged) + Q_PROPERTY(QVariant currentVideo READ currentVideo WRITE setCurrentVideo) + Q_PROPERTY(QVariant currentAudio READ currentAudio WRITE setCurrentAudio) + Q_PROPERTY(QVariant currentSubtitle READ currentSubtitle WRITE setCurrentSubtitle) + Q_PROPERTY(bool subtitleEnabled READ isSubtitleEnabled WRITE setSubtitleEnabled + NOTIFY subtitleEnabledChanged) Q_ENUMS(State) public: - - class VideoRenderer; - - explicit QGstPlayer(QObject *parent = 0, VideoRenderer *renderer = 0); + explicit Player(QObject *parent = 0, VideoRenderer *renderer = 0); typedef GstPlayerError Error; enum State { @@ -58,29 +69,6 @@ public: PLAYING = GST_PLAYER_STATE_PLAYING }; - class VideoRenderer - { - public: - GstPlayerVideoRenderer *renderer(); - virtual GstElement *createVideoSink() = 0; - protected: - VideoRenderer(); - virtual ~VideoRenderer(); - private: - GstPlayerVideoRenderer *renderer_; - }; - - // TODO add remaining bits - class MediaInfo - { - public: - MediaInfo(GstPlayerMediaInfo *media_info); - QString title() const; - bool isSeekable() const; - private: - GstPlayerMediaInfo *mediaInfo_; - }; - QUrl source() const; qint64 duration() const; qint64 position() const; @@ -92,8 +80,11 @@ public: QSize resolution() const; void setResolution(QSize size); bool isVideoAvailable() const; - QQmlPropertyMap *mediaInfo() const; - + MediaInfo *mediaInfo() const; + QVariant currentVideo() const; + QVariant currentAudio() const; + QVariant currentSubtitle() const; + bool isSubtitleEnabled() const; signals: void stateChanged(State new_state); @@ -107,6 +98,7 @@ signals: void mediaInfoChanged(); void sourceChanged(QUrl new_url); void videoAvailableChanged(bool videoAvailable); + void subtitleEnabledChanged(bool enabled); public slots: void play(); @@ -117,27 +109,144 @@ public slots: void setVolume(qreal val); void setMuted(bool val); void setPosition(qint64 pos); + void setCurrentVideo(QVariant track); + void setCurrentAudio(QVariant track); + void setCurrentSubtitle(QVariant track); + void setSubtitleEnabled(bool enabled); private: - Q_DISABLE_COPY(QGstPlayer) - static void onStateChanged(QGstPlayer *, GstPlayerState state); - static void onPositionUpdated(QGstPlayer *, GstClockTime position); - static void onDurationChanged(QGstPlayer *, GstClockTime duration); - static void onBufferingChanged(QGstPlayer *, int percent); - static void onVideoDimensionsChanged(QGstPlayer *, int w, int h); - static void onVolumeChanged(QGstPlayer *); - static void onMuteChanged(QGstPlayer *); - static void onMediaInfoUpdated(QGstPlayer *, GstPlayerMediaInfo *media_info); + Q_DISABLE_COPY(Player) + static void onStateChanged(Player *, GstPlayerState state); + static void onPositionUpdated(Player *, GstClockTime position); + static void onDurationChanged(Player *, GstClockTime duration); + static void onBufferingChanged(Player *, int percent); + static void onVideoDimensionsChanged(Player *, int w, int h); + static void onVolumeChanged(Player *); + static void onMuteChanged(Player *); + static void onMediaInfoUpdated(Player *, GstPlayerMediaInfo *media_info); GstPlayer *player_; State state_; QSize videoDimensions_; - QQmlPropertyMap *mediaInfoMap_; + MediaInfo *mediaInfo_; bool videoAvailable_; + bool subtitleEnabled_; }; -Q_DECLARE_METATYPE(QGstPlayer*) -Q_DECLARE_METATYPE(QGstPlayer::State) +class VideoRenderer +{ +public: + GstPlayerVideoRenderer *renderer(); + virtual GstElement *createVideoSink() = 0; +protected: + VideoRenderer(); + virtual ~VideoRenderer(); +private: + GstPlayerVideoRenderer *renderer_; +}; + +class MediaInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString uri READ uri NOTIFY uriChanged) + Q_PROPERTY(bool seekable READ isSeekable NOTIFY seekableChanged) + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QList videoStreams READ videoStreams CONSTANT) + Q_PROPERTY(QList audioStreams READ audioStreams CONSTANT) + Q_PROPERTY(QList subtitleStreams READ subtitleStreams CONSTANT) + +public: + explicit MediaInfo(Player *player = 0); + QString uri() const; + QString title() const; + bool isSeekable() const; + const QList &videoStreams() const; + const QList &audioStreams() const; + const QList &subtitleStreams() const; + +signals: + void uriChanged(); + void seekableChanged(); + void titleChanged(); + +public Q_SLOTS: + void update(GstPlayerMediaInfo *info); +private: + QString uri_; + QString title_; + bool isSeekable_; + QList videoStreams_; + QList audioStreams_; + QList subtitleStreams_; +}; + +class StreamInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(int index READ index CONSTANT) +public: + int index() const; + +protected: + StreamInfo(GstPlayerStreamInfo* info); +private: + GstPlayerStreamInfo *stream_; + int index_; +}; + +class VideoInfo : public StreamInfo +{ + Q_OBJECT + Q_PROPERTY(QSize resolution READ resolution CONSTANT) +public: + VideoInfo(GstPlayerVideoInfo *info); + QSize resolution() const; + +private: + GstPlayerVideoInfo *video_; + QSize resolution_; +}; + +class AudioInfo : public StreamInfo +{ + Q_OBJECT + Q_PROPERTY(QString language READ language CONSTANT) + Q_PROPERTY(int channels READ channels CONSTANT) + Q_PROPERTY(int bitRate READ bitRate CONSTANT) + Q_PROPERTY(int sampleRate READ sampleRate CONSTANT) + +public: + AudioInfo(GstPlayerAudioInfo *info); + QString const& language() const; + int channels() const; + int bitRate() const; + int sampleRate() const; + +private: + GstPlayerAudioInfo *audio_; + QString language_; + int channels_; + int bitRate_; + int sampleRate_; +}; + +class SubtitleInfo : public StreamInfo +{ + Q_OBJECT + Q_PROPERTY(QString language READ language CONSTANT) +public: + SubtitleInfo(GstPlayerSubtitleInfo *info); + QString const& language() const; + +private: + GstPlayerSubtitleInfo *subtitle_; + QString language_; +}; + +} + +Q_DECLARE_METATYPE(QGstPlayer::Player*) +Q_DECLARE_METATYPE(QGstPlayer::Player::State) extern "C" { From 13156a713d8fc9d9ce6b0742d188e0d258ee9217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 23 Oct 2015 21:54:05 +0300 Subject: [PATCH 134/412] playback/player: qt: Fix -Wunused-variable compiler warnings --- playback/player/qt/qgstplayer.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index e6fc476a34..1dadfa8222 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -626,8 +626,6 @@ gst_player_qt_video_renderer_get_property (GObject * object, static void gst_player_qt_video_renderer_finalize (GObject * object) { - GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); - G_OBJECT_CLASS (gst_player_qt_video_renderer_parent_class)->finalize(object); } @@ -655,14 +653,14 @@ gst_player_qt_video_renderer_class_init } static void -gst_player_qt_video_renderer_init (GstPlayerQtVideoRenderer * self) +gst_player_qt_video_renderer_init (G_GNUC_UNUSED GstPlayerQtVideoRenderer * self) { } static GstElement * gst_player_qt_video_renderer_create_video_sink - (GstPlayerVideoRenderer * iface, GstPlayer *player) + (GstPlayerVideoRenderer * iface, G_GNUC_UNUSED GstPlayer *player) { GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (iface); @@ -713,9 +711,6 @@ static GParamSpec static void gst_player_qt_signal_dispatcher_finalize (GObject * object) { - GstPlayerQtSignalDispatcher *self = - GST_PLAYER_QT_SIGNAL_DISPATCHER (object); - G_OBJECT_CLASS (gst_player_qt_signal_dispatcher_parent_class)->finalize (object); @@ -780,14 +775,14 @@ static void static void gst_player_qt_signal_dispatcher_init - (GstPlayerQtSignalDispatcher * self) + (G_GNUC_UNUSED GstPlayerQtSignalDispatcher * self) { } static void gst_player_qt_signal_dispatcher_dispatch (GstPlayerSignalDispatcher - * iface, GstPlayer * player, void (*emitter) (gpointer data), gpointer data, + * iface, G_GNUC_UNUSED GstPlayer * player, void (*emitter) (gpointer data), gpointer data, GDestroyNotify destroy) { GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (iface); From 3542cae13868648fb4a0202eb66a72d1b6d9c716 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sat, 24 Oct 2015 13:28:10 +0800 Subject: [PATCH 135/412] playback/player: qt: add position update interval property --- playback/player/qt/main.qml | 1 + playback/player/qt/qgstplayer.cpp | 16 +++++++++++++++- playback/player/qt/qgstplayer.h | 9 +++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 9825026f2e..85014c35b9 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -42,6 +42,7 @@ ApplicationWindow { id: player objectName: "player" volume: 0.5 + positionUpdateInterval: 100 onStateChanged: { if (state === Player.STOPPED) { playbutton.state = "play" diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 1dadfa8222..ab140f37a7 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -307,6 +307,13 @@ bool Player::isSubtitleEnabled() const return subtitleEnabled_; } +quint32 Player::positionUpdateInterval() const +{ + Q_ASSERT(player_ != 0); + + return gst_player_get_position_update_interval(player_); +} + void Player::setSubtitleEnabled(bool enabled) { Q_ASSERT(player_ != 0); @@ -318,6 +325,13 @@ void Player::setSubtitleEnabled(bool enabled) emit subtitleEnabledChanged(enabled); } +void Player::setPositionUpdateInterval(quint32 interval) +{ + Q_ASSERT(player_ != 0); + + gst_player_set_position_update_interval(player_, interval); +} + Player::Player(QObject *parent, VideoRenderer *renderer) : QObject(parent) , player_() @@ -356,7 +370,7 @@ Player::onStateChanged(Player * player, GstPlayerState state) void Player::onPositionUpdated(Player * player, GstClockTime position) { - emit player->positionChanged(position); + emit player->positionUpdated(position); } void diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index 1f4dc6f00d..f5d4ab810f 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -42,7 +42,9 @@ class Player : public QObject Q_OBJECT Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) - Q_PROPERTY(qint64 position READ position NOTIFY positionChanged) + Q_PROPERTY(qint64 position READ position NOTIFY positionUpdated) + Q_PROPERTY(quint32 positionUpdateInterval READ positionUpdateInterval + WRITE setPositionUpdateInterval) Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged) Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(int buffering READ buffering NOTIFY bufferingChanged) @@ -85,12 +87,14 @@ public: QVariant currentAudio() const; QVariant currentSubtitle() const; bool isSubtitleEnabled() const; + quint32 positionUpdateInterval() const; + signals: void stateChanged(State new_state); void bufferingChanged(int percent); void enfOfStream(); - void positionChanged(qint64 new_position); + void positionUpdated(qint64 new_position); void durationChanged(qint64 duration); void resolutionChanged(QSize resolution); void volumeChanged(qreal volume); @@ -113,6 +117,7 @@ public slots: void setCurrentAudio(QVariant track); void setCurrentSubtitle(QVariant track); void setSubtitleEnabled(bool enabled); + void setPositionUpdateInterval(quint32 interval); private: Q_DISABLE_COPY(Player) From 512051bdd64d92a185894d7214db5c680eb3f775 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sat, 24 Oct 2015 21:47:38 +0800 Subject: [PATCH 136/412] playback/player: qt: elide long titles Fixes #121 --- playback/player/qt/main.qml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 85014c35b9..b3eb1886ee 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -76,14 +76,6 @@ ApplicationWindow { onTriggered: fileDialog.open() } - menuBar: MenuBar { - Menu { - title: "&File" - MenuItem { action: fileOpenAction } - MenuItem { text: "Quit"; onTriggered: Qt.quit() } - } - } - Item { anchors.fill: parent FontLoader { @@ -452,8 +444,10 @@ ApplicationWindow { height: 38 Text { anchors.centerIn: parent + width: parent.width text: player.mediaInfo.title font.pointSize: 15 + elide: Text.ElideRight } } From d623c45b7608b216bd8b822cd866de5569173e22 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sat, 24 Oct 2015 21:55:37 +0800 Subject: [PATCH 137/412] playback/player: qt: bind video item size to window Fixes #119 --- playback/player/qt/main.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index b3eb1886ee..c75a7b3d4a 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -60,8 +60,8 @@ ApplicationWindow { id: video objectName: "videoItem" anchors.centerIn: parent - width: 640 - height: 480 + width: parent.width + height: parent.height } FileDialog { From dbc1f82678261c192a7ef16bee52a5fc9dafad51 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sun, 25 Oct 2015 01:18:01 +0800 Subject: [PATCH 138/412] playback/player: qt: hide playbar properly Fixes #120 --- playback/player/qt/main.qml | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index c75a7b3d4a..d06b0d48f2 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -64,6 +64,25 @@ ApplicationWindow { height: parent.height } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onPositionChanged: { + playbar.opacity = 1.0 + hidetimer.start() + } + } + + Timer { + id: hidetimer + interval: 5000 + onTriggered: { + playbar.opacity = 0.0 + settings.visible = false + stop() + } + } + FileDialog { id: fileDialog //nameFilters: [TODO globs from mime types] @@ -94,26 +113,6 @@ ApplicationWindow { height: 40//childrenRect.height + 20 radius: 5 - MouseArea { - id: mousearea - anchors.fill: parent - hoverEnabled: true - onEntered: { - parent.opacity = 1.0 - hidetimer.start() - } - } - - Timer { - id: hidetimer - interval: 10000 - onTriggered: { - parent.opacity = 0.0 - settings.visible = false - stop() - } - } - Rectangle { id: settings width: 150; height: settingsView.contentHeight From 5dbeabcb760b1c168ffb27cfc386e4fc7ba6da0b Mon Sep 17 00:00:00 2001 From: Alex Moreno Date: Tue, 27 Oct 2015 17:32:27 +0800 Subject: [PATCH 139/412] playback/player: qt: consistent use of pixel sizes See #124. We should switch everything to be device independent at some point, but this at least gives us a consistent UI for the time being. --- playback/player/qt/main.qml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index d06b0d48f2..9b4d59ddbf 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -144,12 +144,12 @@ ApplicationWindow { width: 150; height: 20 Text { text: model.name - font.pointSize: 13 + font.pixelSize: 13 anchors.centerIn: parent } Text { - font.pointSize: 13 + font.pixelSize: 13 font.family: "FontAwesome" text: FontAwesome.Icon.ChevronRight anchors.right: parent.right @@ -205,7 +205,7 @@ ApplicationWindow { width: 150; height: 20 Text { text: model.modelData.resolution.width + 'x' + model.modelData.resolution.height - font.pointSize: 13 + font.pixelSize: 13 anchors.centerIn: parent } @@ -255,7 +255,7 @@ ApplicationWindow { width: 150; height: 20 Text { text: model.modelData.channels + 'channels' - font.pointSize: 13 + font.pixelSize: 13 anchors.centerIn: parent } @@ -305,7 +305,7 @@ ApplicationWindow { width: 150; height: 20 Text { text: model.modelData.language - font.pointSize: 13 + font.pixelSize: 13 anchors.centerIn: parent } @@ -344,7 +344,7 @@ ApplicationWindow { Text { id : openmedia - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.FolderOpen @@ -360,7 +360,7 @@ ApplicationWindow { Text { anchors.centerIn: parent - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.StepBackward } @@ -382,7 +382,7 @@ ApplicationWindow { PropertyChanges { target: playbutton text: FontAwesome.Icon.PlayCircle - font.pointSize: 25 + font.pixelSize: 25 } }, State { @@ -390,7 +390,7 @@ ApplicationWindow { PropertyChanges { target: playbutton text: FontAwesome.Icon.Pause - font.pointSize: 17 + font.pixelSize: 17 } } ] @@ -417,7 +417,7 @@ ApplicationWindow { Text { anchors.centerIn: parent - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.StepForward } @@ -429,7 +429,7 @@ ApplicationWindow { Text { id: timelabel anchors.centerIn: parent - font.pointSize: 13 + font.pixelSize: 13 color: "black" text: { var current = new Date(Math.floor(slider.value / 1e6)); @@ -445,7 +445,7 @@ ApplicationWindow { anchors.centerIn: parent width: parent.width text: player.mediaInfo.title - font.pointSize: 15 + font.pixelSize: 15 elide: Text.ElideRight } } @@ -456,7 +456,7 @@ ApplicationWindow { Text { id: durationlabel anchors.centerIn: parent - font.pointSize: 13 + font.pixelSize: 13 color: "black" text: { var duration = new Date(Math.floor(player.duration / 1e6)); @@ -467,7 +467,7 @@ ApplicationWindow { Text { id: sub - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.ClosedCaptions color: player.subtitleEnabled ? "red" : "black" @@ -487,7 +487,7 @@ ApplicationWindow { Text { id : volume anchors.centerIn: parent - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: { if (volumeslider.value > volumeslider.maximumValue / 2) { @@ -585,7 +585,7 @@ ApplicationWindow { Text { id: cog - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.Cog @@ -604,7 +604,7 @@ ApplicationWindow { Text { id : fullscreen - font.pointSize: 17 + font.pixelSize: 17 font.family: "FontAwesome" text: FontAwesome.Icon.ResizeFull @@ -661,7 +661,7 @@ ApplicationWindow { x: sliderMouseArea.mouseX Text { - font.pointSize: 13 + font.pixelSize: 13 color: "black" anchors.centerIn: parent text: { From d0bc1108365b7c85298dde848e7442e4a6ad5bbc Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Wed, 28 Oct 2015 00:43:03 +0800 Subject: [PATCH 140/412] playback/player: qt: add a destructor and release resources Fixes #129 --- playback/player/qt/qgstplayer.cpp | 9 +++++++++ playback/player/qt/qgstplayer.h | 1 + 2 files changed, 10 insertions(+) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index ab140f37a7..3aae93d916 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -359,6 +359,15 @@ Player::Player(QObject *parent, VideoRenderer *renderer) gst_player_set_subtitle_track_enabled(player_, false); } +Player::~Player() +{ + if (player_) { + g_signal_handlers_disconnect_by_data(player_, this); + gst_player_stop(player_); + g_object_unref(player_); + } +} + void Player::onStateChanged(Player * player, GstPlayerState state) { diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index f5d4ab810f..c060bc99a1 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -62,6 +62,7 @@ class Player : public QObject public: explicit Player(QObject *parent = 0, VideoRenderer *renderer = 0); + virtual ~Player(); typedef GstPlayerError Error; enum State { From afd72fe79efc720395ff9b80782c0f003f238214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 27 Oct 2015 19:01:58 +0200 Subject: [PATCH 141/412] playback/player: qt: Return a new reference to the renderer from VideoRenderer::renderer() gst_player_new_full() takes ownership of it, and that's where it is usually used. Without this we would create assertions on application shutdown. Fixes #129 --- playback/player/qt/qgstplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 3aae93d916..4fcc975809 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -567,7 +567,7 @@ void Player::setPosition(qint64 pos) GstPlayerVideoRenderer *VideoRenderer::renderer() { - return renderer_; + return static_cast (gst_object_ref (renderer_)); } VideoRenderer::VideoRenderer() From a8f242d48c9771fb86e5909f18b1de51ade45e28 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sat, 31 Oct 2015 11:09:27 +0800 Subject: [PATCH 142/412] playback/player: qt: do not hide playbar if it contains cursor --- playback/player/qt/main.qml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 9b4d59ddbf..8e56328df3 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -77,8 +77,10 @@ ApplicationWindow { id: hidetimer interval: 5000 onTriggered: { - playbar.opacity = 0.0 - settings.visible = false + if (!playbarMouseArea.containsMouse) { + playbar.opacity = 0.0 + settings.visible = false + } stop() } } @@ -101,6 +103,8 @@ ApplicationWindow { source: "fonts/fontawesome-webfont.ttf" } + + Rectangle { id : playbar color: Qt.rgba(1, 1, 1, 0.7) @@ -112,6 +116,13 @@ ApplicationWindow { width : grid.width + 20 height: 40//childrenRect.height + 20 radius: 5 + focus: true + + MouseArea { + id: playbarMouseArea + anchors.fill: parent + hoverEnabled: true + } Rectangle { id: settings From 736ba935187d8973f93fbb8fe06bc43f7e96eff5 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sun, 1 Nov 2015 11:24:31 +0800 Subject: [PATCH 143/412] playback/player: qt: seek while dragging seek bar removed time label on top the seek bar, should be placed elsewhere --- playback/player/qt/main.qml | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 8e56328df3..322bd01444 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -644,9 +644,14 @@ ApplicationWindow { maximumValue: player.duration value: player.position onPressedChanged: player.seek(value) + onValueChanged: { + if (pressed) + player.seek(value) + } enabled: player.mediaInfo.seekable anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter + updateValueWhileDragging: true MouseArea { id: sliderMouseArea @@ -662,27 +667,6 @@ ApplicationWindow { onPressAndHold: mouse.accepted = false; } - Rectangle { - id: hoveredcliptime - width: 40 - height: 17 - color: "lightgray" - anchors.verticalCenter: parent.verticalCenter - visible: sliderMouseArea.containsMouse - x: sliderMouseArea.mouseX - - Text { - font.pixelSize: 13 - color: "black" - anchors.centerIn: parent - text: { - var value = (sliderMouseArea.mouseX - slider.x) * player.duration / (slider.width - slider.x) - var date = new Date(Math.floor(value / 1e6)); - date.getMinutes() + ":" + ('0' + date.getSeconds()).slice(-2) - } - } - } - style: SliderStyle { groove: Item { implicitWidth: playbar.width From 16a9b70155d25c7e32b22e816c04369009ce07ff Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sun, 1 Nov 2015 16:27:13 +0800 Subject: [PATCH 144/412] playback/player: qt: add autoPlay property When set to true will play current media set immediately, and if set to false will show first frame (i.e. go to pause state) --- playback/player/qt/qgstplayer.cpp | 17 +++++++++++++++++ playback/player/qt/qgstplayer.h | 6 ++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 4fcc975809..8117b7439b 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -340,6 +340,7 @@ Player::Player(QObject *parent, VideoRenderer *renderer) , mediaInfo_() , videoAvailable_(false) , subtitleEnabled_(false) + , autoPlay_(false) { player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, @@ -443,9 +444,25 @@ Player::onMediaInfoUpdated(Player *player, GstPlayerMediaInfo *media_info) emit player->mediaInfoChanged(); } + +bool Player::autoPlay() const +{ + return autoPlay_; +} + +void Player::setAutoPlay(bool auto_play) +{ + autoPlay_ = auto_play; + + if (autoPlay_) { + connect(this, SIGNAL(endOfStream()), SLOT(next())); + } +} + QUrl Player::source() const { Q_ASSERT(player_ != 0); + QString url = QString::fromLocal8Bit(gst_player_get_uri(player_)); return QUrl(url); diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index c060bc99a1..afbefd43fc 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -57,6 +57,7 @@ class Player : public QObject Q_PROPERTY(QVariant currentSubtitle READ currentSubtitle WRITE setCurrentSubtitle) Q_PROPERTY(bool subtitleEnabled READ isSubtitleEnabled WRITE setSubtitleEnabled NOTIFY subtitleEnabledChanged) + Q_PROPERTY(bool autoPlay READ autoPlay WRITE setAutoPlay) Q_ENUMS(State) @@ -89,12 +90,11 @@ public: QVariant currentSubtitle() const; bool isSubtitleEnabled() const; quint32 positionUpdateInterval() const; - + bool autoPlay() const; signals: void stateChanged(State new_state); void bufferingChanged(int percent); - void enfOfStream(); void positionUpdated(qint64 new_position); void durationChanged(qint64 duration); void resolutionChanged(QSize resolution); @@ -119,6 +119,7 @@ public slots: void setCurrentSubtitle(QVariant track); void setSubtitleEnabled(bool enabled); void setPositionUpdateInterval(quint32 interval); + void setAutoPlay(bool auto_play); private: Q_DISABLE_COPY(Player) @@ -137,6 +138,7 @@ private: MediaInfo *mediaInfo_; bool videoAvailable_; bool subtitleEnabled_; + bool autoPlay_; }; class VideoRenderer From 1cebdf926c8f9a06f31fc7dc8a6f5cee9d4bc392 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sun, 1 Nov 2015 16:30:43 +0800 Subject: [PATCH 145/412] playback/player: qt: add simple playlist support and wire buttons --- playback/player/qt/main.qml | 12 ++++++ playback/player/qt/qgstplayer.cpp | 68 +++++++++++++++++++++++++++++-- playback/player/qt/qgstplayer.h | 14 ++++++- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 322bd01444..15a8292b2f 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -43,6 +43,8 @@ ApplicationWindow { objectName: "player" volume: 0.5 positionUpdateInterval: 100 + autoPlay: false + onStateChanged: { if (state === Player.STOPPED) { playbutton.state = "play" @@ -375,6 +377,11 @@ ApplicationWindow { font.family: "FontAwesome" text: FontAwesome.Icon.StepBackward } + + MouseArea { + anchors.fill: parent + onPressed: player.previous() + } } Item { @@ -432,6 +439,11 @@ ApplicationWindow { font.family: "FontAwesome" text: FontAwesome.Icon.StepForward } + + MouseArea { + anchors.fill: parent + onPressed: player.next() + } } Item { diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 8117b7439b..8c350d55d3 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -354,7 +354,8 @@ Player::Player(QObject *parent, VideoRenderer *renderer) "swapped-signal::video-dimensions-changed", G_CALLBACK (Player::onVideoDimensionsChanged), this, "swapped-signal::volume-changed", G_CALLBACK (Player::onVolumeChanged), this, "swapped-signal::mute-changed", G_CALLBACK (Player::onMuteChanged), this, - "swapped-signal::media-info-updated", G_CALLBACK (Player::onMediaInfoUpdated), this, NULL); + "swapped-signal::media-info-updated", G_CALLBACK (Player::onMediaInfoUpdated), this, + "swapped-signal::end-of-stream", G_CALLBACK (Player::onEndOfStreamReached), this, NULL); mediaInfo_ = new MediaInfo(this); gst_player_set_subtitle_track_enabled(player_, false); @@ -444,6 +445,63 @@ Player::onMediaInfoUpdated(Player *player, GstPlayerMediaInfo *media_info) emit player->mediaInfoChanged(); } +void Player::onEndOfStreamReached(Player *player) +{ + Q_ASSERT(player != 0); + + emit player->endOfStream(); +} + +void Player::setUri(QUrl url) +{ + Q_ASSERT(player_ != 0); + QByteArray uri = url.toString().toLocal8Bit(); + + gst_player_set_uri(player_, uri.data()); + + autoPlay_ ? play() : pause(); + + emit sourceChanged(url); +} + +QList Player::playlist() const +{ + return playlist_; +} + +void Player::setPlaylist(const QList &playlist) +{ + if (!playlist_.isEmpty()) { + playlist_.erase(playlist_.begin(), playlist_.end()); + } + + playlist_ = playlist; + + iter_ = playlist_.begin(); + setUri(*iter_); +} + +void Player::next() +{ + if (playlist_.isEmpty()) + return; + + if (iter_ == playlist_.end()) + return; + + setUri(*++iter_); +} + +void Player::previous() +{ + if (playlist_.isEmpty()) + return; + + if (iter_ == playlist_.begin()) + return; + + setUri(*--iter_); +} bool Player::autoPlay() const { @@ -554,9 +612,13 @@ void Player::seek(qint64 position) void Player::setSource(QUrl const& url) { Q_ASSERT(player_ != 0); - QByteArray uri = url.toString().toLocal8Bit(); - gst_player_set_uri(player_, uri.data()); + // discard playlist + if (!playlist_.isEmpty()) { + playlist_.erase(playlist_.begin(), playlist_.end()); + } + + setUri(url); emit sourceChanged(url); } diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index afbefd43fc..c41f66c3bb 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -25,7 +25,8 @@ #include #include //#include -#include +#include +#include #include namespace QGstPlayer { @@ -58,6 +59,7 @@ class Player : public QObject Q_PROPERTY(bool subtitleEnabled READ isSubtitleEnabled WRITE setSubtitleEnabled NOTIFY subtitleEnabledChanged) Q_PROPERTY(bool autoPlay READ autoPlay WRITE setAutoPlay) + Q_PROPERTY(QList playlist READ playlist WRITE setPlaylist) Q_ENUMS(State) @@ -91,10 +93,12 @@ public: bool isSubtitleEnabled() const; quint32 positionUpdateInterval() const; bool autoPlay() const; + QList playlist() const; signals: void stateChanged(State new_state); void bufferingChanged(int percent); + void endOfStream(); void positionUpdated(qint64 new_position); void durationChanged(qint64 duration); void resolutionChanged(QSize resolution); @@ -119,6 +123,9 @@ public slots: void setCurrentSubtitle(QVariant track); void setSubtitleEnabled(bool enabled); void setPositionUpdateInterval(quint32 interval); + void setPlaylist(const QList &playlist); + void next(); + void previous(); void setAutoPlay(bool auto_play); private: @@ -131,6 +138,9 @@ private: static void onVolumeChanged(Player *); static void onMuteChanged(Player *); static void onMediaInfoUpdated(Player *, GstPlayerMediaInfo *media_info); + static void onEndOfStreamReached(Player *); + + void setUri(QUrl url); GstPlayer *player_; State state_; @@ -139,6 +149,8 @@ private: bool videoAvailable_; bool subtitleEnabled_; bool autoPlay_; + QList playlist_; + QList::iterator iter_; }; class VideoRenderer From 8853575547c58e379e1fff17858147fc19039c5f Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Sun, 1 Nov 2015 16:32:55 +0800 Subject: [PATCH 146/412] playback/player: qt: accept a list of uris or files as command line parameters --- playback/player/qt/main.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/playback/player/qt/main.cpp b/playback/player/qt/main.cpp index 5634732f6f..999ddcba5e 100644 --- a/playback/player/qt/main.cpp +++ b/playback/player/qt/main.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include +#include #include "player.h" @@ -27,6 +30,20 @@ int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + QCommandLineParser parser; + parser.setApplicationDescription("GstPlayer"); + parser.addHelpOption(); + parser.addPositionalArgument("urls", + QCoreApplication::translate("main", "URLs to play, optionally."), "[urls...]"); + parser.process(app); + + QList media_files; + + const QStringList args = parser.positionalArguments(); + foreach (const QString file, args) { + media_files << QUrl::fromUserInput(file); + } + qmlRegisterType("Player", 1, 0, "Player"); /* the plugin must be loaded before loading the qml file to register the @@ -44,9 +61,12 @@ int main(int argc, char *argv[]) QObject *rootObject = engine.rootObjects().first(); Player *player = rootObject->findChild("player"); + QQuickItem *videoItem = rootObject->findChild("videoItem"); player->setVideoOutput(videoItem); + if (!media_files.isEmpty()) + player->setPlaylist(media_files); return app.exec(); } From e4ae2c3c7bfe14e58e2b73f0c20743dd7ba4bbc2 Mon Sep 17 00:00:00 2001 From: Alexandre Moreno Date: Mon, 2 Nov 2015 01:48:08 +0800 Subject: [PATCH 147/412] playback/player: qt: add new qml item to render media info sample when video is not available it will try to display the sample image returned by media info object. --- playback/player/qt/imagesample.cpp | 61 ++++++++++++++++++++++++++++++ playback/player/qt/imagesample.h | 46 ++++++++++++++++++++++ playback/player/qt/main.cpp | 2 + playback/player/qt/main.qml | 12 ++++++ playback/player/qt/play.pro | 6 ++- playback/player/qt/qgstplayer.cpp | 54 ++++++++++++++++++++++++++ playback/player/qt/qgstplayer.h | 6 +++ 7 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 playback/player/qt/imagesample.cpp create mode 100644 playback/player/qt/imagesample.h diff --git a/playback/player/qt/imagesample.cpp b/playback/player/qt/imagesample.cpp new file mode 100644 index 0000000000..94c07d4e85 --- /dev/null +++ b/playback/player/qt/imagesample.cpp @@ -0,0 +1,61 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "imagesample.h" + +ImageSample::ImageSample() + : QQuickPaintedItem() + , sample_() +{ + +} + +ImageSample::~ImageSample() +{ + +} + +void ImageSample::paint(QPainter *painter) +{ + if (sample_.size().isEmpty()) + return; + + float aspect_ratio = sample_.width() / sample_.height(); + int w = height() * aspect_ratio; + int x = (width() - w) / 2; + + painter->setViewport(x, 0, w, height()); + painter->drawImage(QRectF(0, 0, width(), height()), sample_); +} + +const QImage &ImageSample::sample() const +{ + return sample_; +} + +void ImageSample::setSample(const QImage &sample) +{ + sample_ = sample; + update(); +} + + + diff --git a/playback/player/qt/imagesample.h b/playback/player/qt/imagesample.h new file mode 100644 index 0000000000..917bb24722 --- /dev/null +++ b/playback/player/qt/imagesample.h @@ -0,0 +1,46 @@ +/* GStreamer + * + * Copyright (C) 2015 Alexandre Moreno + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef IMAGESAMPLE_H +#define IMAGESAMPLE_H + +#include +#include +#include +#include +#include "player.h" + +class ImageSample : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(QImage sample READ sample WRITE setSample) +public: + ImageSample(); + ~ImageSample(); + void paint(QPainter *painter); + + const QImage &sample() const; + void setSample(const QImage &sample); + +private: + QImage sample_; +}; + +#endif // IMAGESAMPLE_H diff --git a/playback/player/qt/main.cpp b/playback/player/qt/main.cpp index 999ddcba5e..4ef6b64f79 100644 --- a/playback/player/qt/main.cpp +++ b/playback/player/qt/main.cpp @@ -25,6 +25,7 @@ #include #include "player.h" +#include "imagesample.h" int main(int argc, char *argv[]) { @@ -45,6 +46,7 @@ int main(int argc, char *argv[]) } qmlRegisterType("Player", 1, 0, "Player"); + qmlRegisterType("ImageSample", 1, 0, "ImageSample"); /* the plugin must be loaded before loading the qml file to register the * GstGLVideoItem qml item diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index 15a8292b2f..ce1cf8fb34 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -25,6 +25,7 @@ import QtQuick.Dialogs 1.2 import QtQuick.Window 2.1 import Player 1.0 import org.freedesktop.gstreamer.GLVideoItem 1.0 +import ImageSample 1.0 import "fontawesome.js" as FontAwesome @@ -50,6 +51,7 @@ ApplicationWindow { playbutton.state = "play" } } + onResolutionChanged: { if (player.videoAvailable) { window.width = resolution.width @@ -64,6 +66,16 @@ ApplicationWindow { anchors.centerIn: parent width: parent.width height: parent.height + visible: player.videoAvailable + } + + ImageSample { + id: sample + anchors.centerIn: parent + sample: player.mediaInfo.sample + width: parent.width + height: parent.height + visible: !player.videoAvailable } MouseArea { diff --git a/playback/player/qt/play.pro b/playback/player/qt/play.pro index c8b29549b3..d16440c284 100644 --- a/playback/player/qt/play.pro +++ b/playback/player/qt/play.pro @@ -41,13 +41,15 @@ macx { HEADERS += \ qgstplayer.h \ player.h \ - quickrenderer.h + quickrenderer.h \ + imagesample.h SOURCES += main.cpp \ qgstplayer.cpp \ ../lib/gst/player/gstplayer.c \ ../lib/gst/player/gstplayer-media-info.c \ player.cpp \ - quickrenderer.cpp + quickrenderer.cpp \ + imagesample.cpp DISTFILES += diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 8c350d55d3..b15347ff19 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -25,6 +25,10 @@ #include #include #include +#include + +#include +#include namespace QGstPlayer { @@ -43,6 +47,10 @@ MediaInfo::MediaInfo(Player *player) , uri_() , title_() , isSeekable_(false) + , videoStreams_() + , audioStreams_() + , subtitleStreams_() + , sample_() { } @@ -77,6 +85,11 @@ const QList &MediaInfo::subtitleStreams() const return subtitleStreams_; } +const QImage &MediaInfo::sample() +{ + return sample_; +} + void MediaInfo::update(GstPlayerMediaInfo *info) { Q_ASSERT(info != 0); @@ -146,6 +159,47 @@ void MediaInfo::update(GstPlayerMediaInfo *info) audios->append(new AudioInfo(info)); }, &audioStreams_); + + GstSample *sample; + GstMapInfo map_info; + GstBuffer *buffer; + const GstStructure *caps_struct; + GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED; + + /* get image sample buffer from media */ + sample = gst_player_media_info_get_image_sample (info); + if (!sample) + return; + + buffer = gst_sample_get_buffer (sample); + caps_struct = gst_sample_get_info (sample); + + /* if sample is retrieved from preview-image tag then caps struct + * will not be defined. */ + if (caps_struct) + gst_structure_get_enum (caps_struct, "image-type", + GST_TYPE_TAG_IMAGE_TYPE, reinterpret_cast(&type)); + + /* FIXME: Should we check more type ?? */ + if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) && + (type != GST_TAG_IMAGE_TYPE_UNDEFINED) && + (type != GST_TAG_IMAGE_TYPE_NONE)) { + g_print ("unsupport type ... %d \n", type); + return; + } + + if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ)) { + g_print ("failed to map gst buffer \n"); + return; + } + + sample_ = QImage::fromData(map_info.data, map_info.size); + if (sample_.isNull()) + qWarning() << "failed to load media info sample image"; + + emit sampleChanged(); + + gst_buffer_unmap (buffer, &map_info); } VideoInfo::VideoInfo(GstPlayerVideoInfo *info) diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index c41f66c3bb..8ff87d1e8f 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -27,6 +27,7 @@ //#include #include #include +#include #include namespace QGstPlayer { @@ -174,6 +175,7 @@ class MediaInfo : public QObject Q_PROPERTY(QList videoStreams READ videoStreams CONSTANT) Q_PROPERTY(QList audioStreams READ audioStreams CONSTANT) Q_PROPERTY(QList subtitleStreams READ subtitleStreams CONSTANT) + Q_PROPERTY(QImage sample READ sample NOTIFY sampleChanged) public: explicit MediaInfo(Player *player = 0); @@ -183,11 +185,13 @@ public: const QList &videoStreams() const; const QList &audioStreams() const; const QList &subtitleStreams() const; + const QImage &sample(); signals: void uriChanged(); void seekableChanged(); void titleChanged(); + void sampleChanged(); public Q_SLOTS: void update(GstPlayerMediaInfo *info); @@ -198,6 +202,7 @@ private: QList videoStreams_; QList audioStreams_; QList subtitleStreams_; + QImage sample_; }; class StreamInfo : public QObject @@ -267,6 +272,7 @@ private: Q_DECLARE_METATYPE(QGstPlayer::Player*) Q_DECLARE_METATYPE(QGstPlayer::Player::State) +Q_DECLARE_METATYPE(QGstPlayer::MediaInfo*) extern "C" { From 6cc6793d1f44805a3c10b25c2539c09ee58acf4b Mon Sep 17 00:00:00 2001 From: Justin Kim Date: Wed, 4 Nov 2015 19:59:23 +0900 Subject: [PATCH 148/412] playback/player: gtk-play: change print format of guint64 guint64 type usually corresponds with 'G_GUINT64_FORMAT'. --- playback/player/gtk/gtk-play.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 6815b3d718..0e6ac76f17 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1521,9 +1521,9 @@ update_position_label (GtkLabel * label, guint64 seconds) seconds -= mins * 60; if (hrs) - data = g_strdup_printf ("%d:%02d:%02ld", hrs, mins, seconds); + data = g_strdup_printf ("%d:%02d:%02" G_GUINT64_FORMAT, hrs, mins, seconds); else - data = g_strdup_printf ("%02d:%02ld", mins, seconds); + data = g_strdup_printf ("%02d:%02" G_GUINT64_FORMAT, mins, seconds); gtk_label_set_label (label, data); g_free (data); From 9ffdefc14f2f3877fe4eb2b51595fc618b40ff70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 6 Nov 2015 11:03:56 +0100 Subject: [PATCH 149/412] playback/player: Revert "README.md formatting." This reverts commit 499f68c42371081204b6285227073a7eb165c652. The gradle based build system has various problems currently. --- playback/player/android/README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/playback/player/android/README.md b/playback/player/android/README.md index 2f8318fb46..cf331f7b97 100644 --- a/playback/player/android/README.md +++ b/playback/player/android/README.md @@ -9,16 +9,10 @@ Prerequisites 3. If you have a different special directory for pkg-config or other tools (e.g. on OSX when using Homebrew), then also set this path using the `ndk.extraPath` variable in **local.properties** 4. Download the GStreamer android ports http://gstreamer.freedesktop.org/data/pkg/android/ and set `gstreamer.$ABI.dir` properties in **local.properties**: -Sample local.properties: - - sdk.dir=/path/to/android-sdk/ - ndk.dir=/path/to/android-ndk/ - ndk.extraPath=/usr/local/bin gstreamer.arm.dir=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ gstreamer.armv7.dir=/path/to/gstreamer-1.0-android-armv7-release-1.4.5/ gstreamer.x86.dir=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ - Compiling the sample -------------------- @@ -53,4 +47,4 @@ If you don't want to build all architectures, please modify the file `app/src/ma Finally, within the `app/src/main/` directory, invoke: - ndk-build + ndk-build \ No newline at end of file From 9d8f476174408789fb42f03300425678decb8e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 6 Nov 2015 11:04:14 +0100 Subject: [PATCH 150/412] playback/player: Revert "android: Fix Windows build of the app" This reverts commit 5d8c1868beec85ac02bcc6e0bfd0ad9dbb99a3b8. The gradle based build system has various problems currently. --- playback/player/android/app/build.gradle | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 18e68bd205..6039b5bd7c 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -1,5 +1,3 @@ -import org.apache.tools.ant.taskdefs.condition.Os - apply plugin: 'com.android.application' android { @@ -59,12 +57,7 @@ android { environment PATH: "${System.getenv("PATH")}${File.pathSeparator}${ndkExtraPath}" } - // Enable V=1 for debugging messages. - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine "${ndkDir}/ndk-build.cmd", '-C', file('src/main/jni').absolutePath //, 'V=1' - } else { - commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' - } + commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' // Enable V=1 for debugging messages. } } From 1d5a020a034276e270e4b54dbd2e6e4020b0abe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 6 Nov 2015 11:04:17 +0100 Subject: [PATCH 151/412] playback/player: Revert "android: Use gradle & ndk-build combo to generate the Android App" This reverts commit a95ee9c61c371ec2b8d4ff59cace26451b11225a. The gradle based build system has various problems currently. --- playback/player/android/AndroidManifest.xml | 79 +++++++++ playback/player/android/Makefile.am | 32 +--- playback/player/android/README.md | 50 ------ playback/player/android/app/app.iml | 97 ----------- playback/player/android/app/build.gradle | 68 -------- .../android/app/src/main/AndroidManifest.xml | 97 ----------- .../gstreamer/play/VideoSelector.java | 151 ---------------- .../android/app/src/main/jni/Application.mk | 2 - .../src/main/res/layout/activity_player.xml | 70 -------- .../res/layout/activity_video_selector.xml | 15 -- .../src/main/res/menu/menu_video_selector.xml | 17 -- .../app/src/main/res/values-w820dp/dimens.xml | 6 - .../app/src/main/res/values/dimens.xml | 5 - .../app/src/main/res/values/styles.xml | 7 - playback/player/android/build.gradle | 15 -- playback/player/android/build.xml | 92 ++++++++++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - playback/player/android/gradlew | 164 ------------------ playback/player/android/gradlew.bat | 90 ---------- .../android/{app/src/main => }/jni/Android.mk | 21 +-- .../android/{app/src/main => }/jni/player.c | 2 +- playback/player/android/res/layout/main.xml | 62 +++++++ .../{app/src/main => }/res/values/strings.xml | 3 - playback/player/android/settings.gradle | 1 - .../org/freedesktop/gstreamer/Player.java | 0 .../player}/GStreamerSurfaceView.java | 0 .../freedesktop/gstreamer/player}/Play.java | 60 +++---- 28 files changed, 280 insertions(+), 932 deletions(-) create mode 100644 playback/player/android/AndroidManifest.xml delete mode 100644 playback/player/android/README.md delete mode 100644 playback/player/android/app/app.iml delete mode 100644 playback/player/android/app/build.gradle delete mode 100644 playback/player/android/app/src/main/AndroidManifest.xml delete mode 100644 playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java delete mode 100644 playback/player/android/app/src/main/jni/Application.mk delete mode 100644 playback/player/android/app/src/main/res/layout/activity_player.xml delete mode 100644 playback/player/android/app/src/main/res/layout/activity_video_selector.xml delete mode 100644 playback/player/android/app/src/main/res/menu/menu_video_selector.xml delete mode 100644 playback/player/android/app/src/main/res/values-w820dp/dimens.xml delete mode 100644 playback/player/android/app/src/main/res/values/dimens.xml delete mode 100644 playback/player/android/app/src/main/res/values/styles.xml delete mode 100644 playback/player/android/build.gradle create mode 100644 playback/player/android/build.xml delete mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 playback/player/android/gradlew delete mode 100644 playback/player/android/gradlew.bat rename playback/player/android/{app/src/main => }/jni/Android.mk (60%) rename playback/player/android/{app/src/main => }/jni/player.c (99%) create mode 100644 playback/player/android/res/layout/main.xml rename playback/player/android/{app/src/main => }/res/values/strings.xml (70%) delete mode 100644 playback/player/android/settings.gradle rename playback/player/android/{app/src/main/java => src}/org/freedesktop/gstreamer/Player.java (100%) rename playback/player/android/{app/src/main/java/org/freedesktop/gstreamer/play => src/org/freedesktop/gstreamer/player}/GStreamerSurfaceView.java (100%) rename playback/player/android/{app/src/main/java/org/freedesktop/gstreamer/play => src/org/freedesktop/gstreamer/player}/Play.java (81%) diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/AndroidManifest.xml new file mode 100644 index 0000000000..8b156d49d0 --- /dev/null +++ b/playback/player/android/AndroidManifest.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/Makefile.am b/playback/player/android/Makefile.am index e9e4c74615..2664abbab9 100644 --- a/playback/player/android/Makefile.am +++ b/playback/player/android/Makefile.am @@ -1,24 +1,10 @@ EXTRA_DIST = \ - build.gradle \ - gradlew \ - gradlew.bat \ - README.md \ - settings.gradle \ - app/build.gradle \ - app/src/main/AndroidManifest.xml \ - app/src/main/java/org/freedesktop/gstreamer/Player.java \ - app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ - app/src/main/java/org/freedesktop/gstreamer/player/Play.java \ - app/src/main/java/org/freedesktop/gstreamer/player/VideoSelector.java \ - app/src/main/jni/Android.mk \ - app/src/main/jni/Application.mk \ - app/src/main/jni/player.c \ - app/src/main/res/layout/activity_player.xml \ - app/src/main/res/layout/activity_video_selector.xml \ - app/src/main/res/menu/menu_video_selector.xml \ - app/src/main/res/values/dimens.xml \ - app/src/main/res/values/strings.xml \ - app/src/main/res/values/styles.xml \ - app/src/main/res/values-w820dp/styles.xml \ - gradle/wrapper/gradle-wrapper.jar \ - gradle/wrapper/gradle-wrapper.properties \ No newline at end of file + AndroidManifest.xml \ + jni/Android.mk \ + jni/player.c \ + res/layout/main.xml \ + res/values/strings.xml \ + src/org/freedesktop/gstreamer/Player.java \ + src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ + src/org/freedesktop/gstreamer/player/Play.java + diff --git a/playback/player/android/README.md b/playback/player/android/README.md deleted file mode 100644 index cf331f7b97..0000000000 --- a/playback/player/android/README.md +++ /dev/null @@ -1,50 +0,0 @@ -GST Player Android port -======================= - -Prerequisites -------------- - -1. Install Android SDK from https://developer.android.com/sdk/ & set `sdk.dir` in **local.properties** to the installation path -2. Install Android NDK from https://developer.android.com/tools/sdk/ndk/index.html & set `ndk.dir` in **local.properties** to the installation path -3. If you have a different special directory for pkg-config or other tools (e.g. on OSX when using Homebrew), then also set this path using the `ndk.extraPath` variable in **local.properties** -4. Download the GStreamer android ports http://gstreamer.freedesktop.org/data/pkg/android/ and set `gstreamer.$ABI.dir` properties in **local.properties**: - - gstreamer.arm.dir=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ - gstreamer.armv7.dir=/path/to/gstreamer-1.0-android-armv7-release-1.4.5/ - gstreamer.x86.dir=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ - -Compiling the sample --------------------- - -Use - - ./gradlew installDebug - -to compile and install a debug version onto all connected devices. - -Please note this component is using the new Android build system based on Gradle. More information about this is available on http://tools.android.com/tech-docs/new-build-system. - -Android Studio --------------- - -Android Studio builds will work out of the box. Simply open `build.gradle` in this folder to import the project. - -Manual NDK build ----------------- - -It is still possible to build just the NDK portion. This will speed up the process a bit as you don't need to start gradle first and compile the complete App. -First, make sure to set `NDK_PROJECT_PATH` to this projects main source path. Additionally the SDK & NDK tools are available in `$PATH`. - - export NDK_PROJECT_PATH=$PWD/app/src/main - -Second, set the following environment variables to the GStreamer installation folders: - - export GSTREAMER_ROOT_ARM=/path/to/gstreamer-1.0-android-arm-release-1.4.5/ - export GSTREAMER_ROOT_ARMV7=/path/to/tmp/gstreamer-1.0-android-armv7-release-1.4.5/ - export GSTREAMER_ROOT_X86=/path/to/gstreamer-1.0-android-x86-release-1.4.5/ - -If you don't want to build all architectures, please modify the file `app/src/main/jni/Application.mk` - -Finally, within the `app/src/main/` directory, invoke: - - ndk-build \ No newline at end of file diff --git a/playback/player/android/app/app.iml b/playback/player/android/app/app.iml deleted file mode 100644 index 86e3b56426..0000000000 --- a/playback/player/android/app/app.iml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle deleted file mode 100644 index 6039b5bd7c..0000000000 --- a/playback/player/android/app/build.gradle +++ /dev/null @@ -1,68 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" - - defaultConfig { - applicationId "org.freedesktop.gstreamer.play" - minSdkVersion 9 - targetSdkVersion 22 - - ndk { - moduleName "gstplayer" - } - } - - sourceSets { - main { - // GStreamer will generate these files. - java { - srcDir 'src/main/jni/src' - } - assets { - srcDir 'src/main/jni/assets' - } - - //Tell Gradle where to put the compiled shared library - jniLibs.srcDir 'src/main/libs' - - //disable automatic ndk-build call - jni.srcDirs = []; - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' - } - } - - // Thank you http://stackoverflow.com/q/28878689/375209 - tasks.withType(JavaCompile) { - compileTask -> compileTask.dependsOn ndkBuild - } - - task ndkBuild(type: Exec) { - Properties properties = new Properties() - properties.load(project.rootProject.file('local.properties').newDataInputStream()) - def ndkDir = properties.getProperty('ndk.dir') - environment GSTREAMER_ROOT_ARM: properties.getProperty('gstreamer.arm.dir') - environment GSTREAMER_ROOT_ARMV7: properties.getProperty('gstreamer.armv7.dir') - environment GSTREAMER_ROOT_X86: properties.getProperty('gstreamer.x86.dir') - - def ndkExtraPath = properties.getProperty('ndk.extraPath') - if (! "".equalsIgnoreCase(ndkExtraPath)) { - environment PATH: "${System.getenv("PATH")}${File.pathSeparator}${ndkExtraPath}" - } - - commandLine "${ndkDir}/ndk-build", '-C', file('src/main/jni').absolutePath //, 'V=1' // Enable V=1 for debugging messages. - } -} - -dependencies { - compile 'com.android.support:appcompat-v7:22.1.1' - compile "com.android.support:recyclerview-v7:22.1.1" - compile "com.android.support:support-annotations:22.1.1" -} diff --git a/playback/player/android/app/src/main/AndroidManifest.xml b/playback/player/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 6de668cec0..0000000000 --- a/playback/player/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java deleted file mode 100644 index 38739f7d12..0000000000 --- a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/VideoSelector.java +++ /dev/null @@ -1,151 +0,0 @@ -/* GStreamer - * - * Copyright (C) 2015 Sebastian Roth - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -package org.freedesktop.gstreamer.play; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.MediaStore; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v4.widget.SimpleCursorAdapter; -import android.support.v7.app.AppCompatActivity; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - - -public class VideoSelector extends AppCompatActivity implements LoaderManager.LoaderCallbacks { - - VideoAdapter adapter; - boolean sortByName = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_video_selector); - - ListView videoList = (ListView) findViewById(R.id.videoList); - adapter = new VideoAdapter(this); - videoList.setAdapter(adapter); - videoList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, final int position, final long id) { - final String videoPath = adapter.getVideoPath(position); - if (!TextUtils.isEmpty(videoPath)) { - Intent intent = new Intent(VideoSelector.this, Play.class); - intent.setData(Uri.parse("file://" + videoPath)); - startActivity(intent); - } - } - }); - - getSupportLoaderManager().initLoader(1, null, this); - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_video_selector, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_about) { - return true; - } - - //noinspection SimplifiableIfStatement - if (id == R.id.action_sort) { - sortByName = !sortByName; - getSupportLoaderManager().restartLoader(1, null, this); - return true; - } - - - return super.onOptionsItemSelected(item); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - if (sortByName) { - return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, - "UPPER(" + MediaStore.Video.Media.DATA + ")"); - } else { - return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, - "UPPER(" + MediaStore.Video.Media.DISPLAY_NAME + ")"); - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - adapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader loader) { - - } - - - class VideoAdapter extends SimpleCursorAdapter { - public VideoAdapter(Context context) { - super(context, android.R.layout.simple_list_item_2, null, - new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, - new int[]{android.R.id.text1, android.R.id.text2}, 0); - } - - @Override - public long getItemId(int position) { - final Cursor cursor = getCursor(); - if (cursor.getCount() == 0 || position >= cursor.getCount()) { - return 0; - } - cursor.moveToPosition(position); - - return cursor.getLong(0); - } - - public String getVideoPath(int position) { - final Cursor cursor = getCursor(); - if (cursor.getCount() == 0) { - return ""; - } - cursor.moveToPosition(position); - - return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); - } - } -} diff --git a/playback/player/android/app/src/main/jni/Application.mk b/playback/player/android/app/src/main/jni/Application.mk deleted file mode 100644 index f665244472..0000000000 --- a/playback/player/android/app/src/main/jni/Application.mk +++ /dev/null @@ -1,2 +0,0 @@ -APP_ABI := armeabi armeabi-v7a x86 -APP_PLATFORM := android-9 \ No newline at end of file diff --git a/playback/player/android/app/src/main/res/layout/activity_player.xml b/playback/player/android/app/src/main/res/layout/activity_player.xml deleted file mode 100644 index cd4a933791..0000000000 --- a/playback/player/android/app/src/main/res/layout/activity_player.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/app/src/main/res/layout/activity_video_selector.xml b/playback/player/android/app/src/main/res/layout/activity_video_selector.xml deleted file mode 100644 index 229ae17845..0000000000 --- a/playback/player/android/app/src/main/res/layout/activity_video_selector.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/playback/player/android/app/src/main/res/menu/menu_video_selector.xml b/playback/player/android/app/src/main/res/menu/menu_video_selector.xml deleted file mode 100644 index 93f44374d6..0000000000 --- a/playback/player/android/app/src/main/res/menu/menu_video_selector.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/playback/player/android/app/src/main/res/values-w820dp/dimens.xml b/playback/player/android/app/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816444..0000000000 --- a/playback/player/android/app/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/playback/player/android/app/src/main/res/values/dimens.xml b/playback/player/android/app/src/main/res/values/dimens.xml deleted file mode 100644 index 47c8224673..0000000000 --- a/playback/player/android/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 16dp - 16dp - diff --git a/playback/player/android/app/src/main/res/values/styles.xml b/playback/player/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 540f2fe87c..0000000000 --- a/playback/player/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/playback/player/android/build.gradle b/playback/player/android/build.gradle deleted file mode 100644 index 01de6eff51..0000000000 --- a/playback/player/android/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:1.2.2' - } -} - -allprojects { - repositories { - jcenter() - } -} diff --git a/playback/player/android/build.xml b/playback/player/android/build.xml new file mode 100644 index 0000000000..4ef18c8f98 --- /dev/null +++ b/playback/player/android/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/gradle/wrapper/gradle-wrapper.jar b/playback/player/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/playback/player/android/gradlew.bat b/playback/player/android/gradlew.bat deleted file mode 100644 index 8a0b282aa6..0000000000 --- a/playback/player/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/playback/player/android/app/src/main/jni/Android.mk b/playback/player/android/jni/Android.mk similarity index 60% rename from playback/player/android/app/src/main/jni/Android.mk rename to playback/player/android/jni/Android.mk index 7afe06251e..fe4d50aaa8 100644 --- a/playback/player/android/app/src/main/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -2,25 +2,18 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -GST_PATH := $(LOCAL_PATH)/../../../../../ - LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := player.c \ - $(GST_PATH)/lib/gst/player/gstplayer.c \ - $(GST_PATH)/lib/gst/player/gstplayer-media-info.c -LOCAL_C_INCLUDES := $(GST_PATH)/lib +LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c ../../lib/gst/player/gstplayer-media-info.c +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../lib LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) -ifeq ($(TARGET_ARCH_ABI),armeabi) - GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM) -else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) - GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARMV7) -else ifeq ($(TARGET_ARCH_ABI),x86) - GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86) -else - $(error Target arch ABI $(TARGET_ARCH_ABI) not supported) +ifndef GSTREAMER_ROOT +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID) endif GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ diff --git a/playback/player/android/app/src/main/jni/player.c b/playback/player/android/jni/player.c similarity index 99% rename from playback/player/android/app/src/main/jni/player.c rename to playback/player/android/jni/player.c index 133d97090f..fc552476af 100644 --- a/playback/player/android/app/src/main/jni/player.c +++ b/playback/player/android/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "gst/player/player.h" +#include "gst/player/gstplayer.h" GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category diff --git a/playback/player/android/res/layout/main.xml b/playback/player/android/res/layout/main.xml new file mode 100644 index 0000000000..b745d8069e --- /dev/null +++ b/playback/player/android/res/layout/main.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/playback/player/android/app/src/main/res/values/strings.xml b/playback/player/android/res/values/strings.xml similarity index 70% rename from playback/player/android/app/src/main/res/values/strings.xml rename to playback/player/android/res/values/strings.xml index d3af9a3e35..9587e3c40c 100644 --- a/playback/player/android/app/src/main/res/values/strings.xml +++ b/playback/player/android/res/values/strings.xml @@ -3,7 +3,4 @@ GStreamer Play Play Pause - - About - Sort diff --git a/playback/player/android/settings.gradle b/playback/player/android/settings.gradle deleted file mode 100644 index e7b4def49c..0000000000 --- a/playback/player/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java similarity index 100% rename from playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java rename to playback/player/android/src/org/freedesktop/gstreamer/Player.java diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/GStreamerSurfaceView.java b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java similarity index 100% rename from playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/GStreamerSurfaceView.java rename to playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java similarity index 81% rename from playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java rename to playback/player/android/src/org/freedesktop/gstreamer/player/Play.java index 9be9d78b2a..2874f05de5 100644 --- a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/play/Play.java +++ b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java @@ -20,13 +20,18 @@ package org.freedesktop.gstreamer.play; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.PowerManager; -import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; @@ -37,16 +42,13 @@ import android.widget.Toast; import org.freedesktop.gstreamer.Player; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -public class Play extends AppCompatActivity implements SurfaceHolder.Callback, OnSeekBarChangeListener { +public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener { private PowerManager.WakeLock wake_lock; private Player player; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) + { super.onCreate(savedInstanceState); try { @@ -57,7 +59,7 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O return; } - setContentView(R.layout.activity_player); + setContentView(R.layout.main); player = new Player(); @@ -86,7 +88,7 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O player.setPositionUpdatedListener(new Player.PositionUpdatedListener() { public void positionUpdated(Player player, final long position) { - runOnUiThread(new Runnable() { + runOnUiThread (new Runnable() { public void run() { sb.setProgress((int) (position / 1000000)); updateTimeWidget(); @@ -97,7 +99,7 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O player.setDurationChangedListener(new Player.DurationChangedListener() { public void durationChanged(Player player, final long duration) { - runOnUiThread(new Runnable() { + runOnUiThread (new Runnable() { public void run() { sb.setMax((int) (duration / 1000000)); updateTimeWidget(); @@ -107,35 +109,31 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O }); final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video); - player.setVideoDimensionsChangedListener(new Player.VideoDimensionsChangedListener() { public void videoDimensionsChanged(Player player, final int width, final int height) { - runOnUiThread(new Runnable() { + runOnUiThread (new Runnable() { public void run() { - Log.i("GStreamer", "Media size changed to " + width + "x" + height); - if (width > 0 && height > 0) { - gsv.media_width = width; - gsv.media_height = height; - runOnUiThread(new Runnable() { - public void run() { - gsv.requestLayout(); - } - }); - } else { - Log.i("GStreamer", "Ignoring media size."); - } + Log.i ("GStreamer", "Media size changed to " + width + "x" + height); + gsv.media_width = width; + gsv.media_height = height; + runOnUiThread(new Runnable() { + public void run() { + gsv.requestLayout(); + } + }); } }); } }); - SurfaceHolder sh = gsv.getHolder(); + SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); + SurfaceHolder sh = sv.getHolder(); sh.addCallback(this); String mediaUri = null; Intent intent = getIntent(); android.net.Uri uri = intent.getData(); - Log.i("GStreamer", "Received URI: " + uri); + Log.i ("GStreamer", "Received URI: " + uri); if (uri.getScheme().equals("content")) { android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null); cursor.moveToFirst(); @@ -154,7 +152,7 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O super.onDestroy(); } - private void updateTimeWidget() { + private void updateTimeWidget () { final TextView tv = (TextView) this.findViewById(R.id.textview_time); final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); final int pos = sb.getProgress(); @@ -162,12 +160,14 @@ public class Play extends AppCompatActivity implements SurfaceHolder.Callback, O SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); df.setTimeZone(TimeZone.getTimeZone("UTC")); - final String message = df.format(new Date(pos)) + " / " + df.format(new Date(max)); + final String message = df.format(new Date (pos)) + " / " + df.format(new Date (max)); tv.setText(message); } - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.d("GStreamer", "Surface changed to format " + format + " width " + width + " height " + height); + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + + width + " height " + height); player.setSurface(holder.getSurface()); } From de7130efc268a77beeba567012c85a119531d0ac Mon Sep 17 00:00:00 2001 From: caseten Date: Sat, 7 Nov 2015 13:09:47 -0700 Subject: [PATCH 152/412] playback/player: android: Fix typo for restricted codecs plugin --- playback/player/android/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index fe4d50aaa8..9d2ff4cc1d 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -19,7 +19,7 @@ endif GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk -GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 glib-2.0 include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk From 171c7235929ff0fb88d10e345d34260ad333a4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 9 Nov 2015 21:35:57 +0100 Subject: [PATCH 154/412] playback/player: gtk: Remove double assignment of a variable --- playback/player/gtk/gtk-video-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index b784f4ac0b..b34f927370 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -107,7 +107,7 @@ gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) GstElement *gtk_sink = gst_element_factory_make ("gtkglsink", NULL); if (gtk_sink) { - GstElement *sink = sink = gst_element_factory_make ("glsinkbin", NULL); + GstElement *sink = gst_element_factory_make ("glsinkbin", NULL); g_object_set (sink, "sink", gtk_sink, NULL); self->sink = sink; From 00cad0da116ee809eeb8cdfaad19c03ae5e231cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 13 Nov 2015 10:36:24 +0100 Subject: [PATCH 155/412] playback/player: android: Add more URI schemes, mimetypes and file extensions Based on the AndroidManifest.xml of VLC for Android. --- playback/player/android/AndroidManifest.xml | 331 ++++++++++++++++++-- 1 file changed, 300 insertions(+), 31 deletions(-) diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/AndroidManifest.xml index 8b156d49d0..41d56522a2 100644 --- a/playback/player/android/AndroidManifest.xml +++ b/playback/player/android/AndroidManifest.xml @@ -11,12 +11,7 @@ - - - + @@ -26,9 +21,27 @@ + + + + + + + + + + + + + + + + + + - @@ -37,43 +50,299 @@ - + + + + + + + + + + - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From eaed96145e604b4e543255f76e5903605aa9448d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 13 Nov 2015 10:36:52 +0100 Subject: [PATCH 156/412] playback/player: android: Don't do custom surface width/height calculations if we have no media width/height --- .../freedesktop/gstreamer/player/GStreamerSurfaceView.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java index f2dd8a9bf0..075f035880 100644 --- a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java +++ b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java @@ -50,6 +50,11 @@ public class GStreamerSurfaceView extends SurfaceView { // we are given the freedom to do so. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (media_width == 0 || media_height == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + int width = 0, height = 0; int wmode = View.MeasureSpec.getMode(widthMeasureSpec); int hmode = View.MeasureSpec.getMode(heightMeasureSpec); From af371bf38e47442290f11c816ba34c6cbf7160d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 9 Dec 2015 09:48:38 +0200 Subject: [PATCH 157/412] playback/player: android: Allow building for multiple Android ABIs --- playback/player/android/jni/Android.mk | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 9d2ff4cc1d..4766fb4a6f 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -9,6 +9,20 @@ LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM) +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARMV7) +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM64) +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86) +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86_64) +else +$(error Target arch ABI not supported) +endif + ifndef GSTREAMER_ROOT ifndef GSTREAMER_ROOT_ANDROID $(error GSTREAMER_ROOT_ANDROID is not defined!) From 8ccf31fcf963b9933f1b71fad6a7c3a11c89d182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 22 Dec 2015 12:08:53 +0100 Subject: [PATCH 158/412] playback/player: android: Fix compilation --- playback/player/android/jni/Android.mk | 11 ++++++++++- playback/player/android/jni/player.c | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 4766fb4a6f..77af905fb9 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -3,7 +3,16 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := player.c ../../lib/gst/player/gstplayer.c ../../lib/gst/player/gstplayer-media-info.c +LOCAL_SRC_FILES := \ + player.c \ + ../../lib/gst/player/gstplayer.c \ + ../../lib/gst/player/gstplayer-signal-dispatcher.c \ + ../../lib/gst/player/gstplayer-video-renderer.c \ + ../../lib/gst/player/gstplayer-media-info.c \ + ../../lib/gst/player/gstplayer-g-main-context-signal-dispatcher.c \ + ../../lib/gst/player/gstplayer-video-overlay-video-renderer.c \ + ../../lib/gst/player/gstplayer-visualization.c + LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../lib LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index fc552476af..133d97090f 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "gst/player/gstplayer.h" +#include "gst/player/player.h" GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category From addb63586bad7a36d82caa9b30bd10445dd61264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 4 Jan 2016 09:59:40 +0200 Subject: [PATCH 159/412] playback/player: player: Remove gst_player_new() and make gst_player_new_full() the normal constructor In very few cases the simple version was actually needed and having the parameters hidden by a _full() version caused application that actually needed it to not use it. --- playback/player/android/jni/player.c | 2 +- playback/player/gst-play/gst-play.c | 2 +- playback/player/gtk/gtk-play.c | 2 +- playback/player/ios/GstPlay/VideoViewController.m | 2 +- playback/player/qt/qgstplayer.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index 133d97090f..ee537086aa 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -199,7 +199,7 @@ native_new (JNIEnv * env, jobject thiz) Player *player = g_new0 (Player, 1); player->renderer = gst_player_video_overlay_video_renderer_new (NULL); - player->player = gst_player_new_full (player->renderer, NULL); + player->player = gst_player_new (player->renderer, NULL); SET_CUSTOM_DATA (env, thiz, native_player_field_id, player); player->java_player = (*env)->NewGlobalRef (env, thiz); diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 95b9d4fa1c..46f6d62a71 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -357,7 +357,7 @@ play_new (gchar ** uris, gdouble initial_volume) play->cur_idx = -1; play->player = - gst_player_new_full (NULL, gst_player_g_main_context_signal_dispatcher_new + gst_player_new (NULL, gst_player_g_main_context_signal_dispatcher_new (NULL)); g_signal_connect (play->player, "position-updated", diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 0e6ac76f17..51d0bf0235 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1732,7 +1732,7 @@ gtk_play_constructor (GType type, guint n_construct_params, create_ui (self); self->player = - gst_player_new_full (self->renderer, + gst_player_new (self->renderer, gst_player_g_main_context_signal_dispatcher_new (NULL)); g_signal_connect (self->player, "position-updated", diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index eda419c0e2..19c379926c 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -63,7 +63,7 @@ media_width = 320; media_height = 240; - player = gst_player_new_full (gst_player_video_overlay_video_renderer_new ((__bridge gpointer)(video_view)), NULL); + player = gst_player_new (gst_player_video_overlay_video_renderer_new ((__bridge gpointer)(video_view)), NULL); g_object_set (player, "uri", [uri UTF8String], NULL); gst_debug_set_threshold_for_name("gst-player", GST_LEVEL_TRACE); diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index b15347ff19..77112ef346 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -397,7 +397,7 @@ Player::Player(QObject *parent, VideoRenderer *renderer) , autoPlay_(false) { - player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, + player_ = gst_player_new(renderer ? renderer->renderer() : 0, gst_player_qt_signal_dispatcher_new(this)); g_object_connect(player_, From f509043c1d18bf7b25ddec60543670346b4282f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 4 Jan 2016 10:11:20 +0200 Subject: [PATCH 160/412] playback/player: player: Build against GstPlayer version from gst-plugins-bad everywhere The old version with everything included and buildable against GStreamer 1.6 can still be found in the gst-player-0.1 branch and will be continued to be updated for a while. --- playback/player/android/jni/Android.mk | 13 ++------ playback/player/android/jni/player.c | 2 +- playback/player/gst-play/Makefile.am | 5 ++- playback/player/gtk/Makefile.am | 5 ++- playback/player/gtk/gtk-play.c | 1 - .../ios/GstPlay.xcodeproj/project.pbxproj | 33 ------------------- .../player/ios/GstPlay/VideoViewController.m | 2 +- playback/player/qt/play.pro | 11 ++----- 8 files changed, 10 insertions(+), 62 deletions(-) diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk index 77af905fb9..65e2044d99 100644 --- a/playback/player/android/jni/Android.mk +++ b/playback/player/android/jni/Android.mk @@ -3,17 +3,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := gstplayer -LOCAL_SRC_FILES := \ - player.c \ - ../../lib/gst/player/gstplayer.c \ - ../../lib/gst/player/gstplayer-signal-dispatcher.c \ - ../../lib/gst/player/gstplayer-video-renderer.c \ - ../../lib/gst/player/gstplayer-media-info.c \ - ../../lib/gst/player/gstplayer-g-main-context-signal-dispatcher.c \ - ../../lib/gst/player/gstplayer-video-overlay-video-renderer.c \ - ../../lib/gst/player/gstplayer-visualization.c +LOCAL_SRC_FILES := player.c -LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../lib LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) @@ -43,6 +34,6 @@ GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) -GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 glib-2.0 +GSTREAMER_EXTRA_DEPS := gstreamer-player-1.0 gstreamer-video-1.0 glib-2.0 include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index ee537086aa..ed72e86f75 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -25,7 +25,7 @@ #include #include -#include "gst/player/player.h" +#include GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category diff --git a/playback/player/gst-play/Makefile.am b/playback/player/gst-play/Makefile.am index 534e0df83f..184e8c1aa1 100644 --- a/playback/player/gst-play/Makefile.am +++ b/playback/player/gst-play/Makefile.am @@ -2,9 +2,8 @@ bin_PROGRAMS = gst-play gst_play_SOURCES = gst-play.c gst-play-kb.c gst-play-kb.h -LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ - $(GSTREAMER_LIBS) $(GLIB_LIBS) $(LIBM) +LDADD = $(GSTREAMER_LIBS) $(GLIB_LIBS) $(LIBM) -AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GLIB_CFLAGS) $(WARNING_CFLAGS) +AM_CFLAGS = $(GSTREAMER_CFLAGS) $(GLIB_CFLAGS) $(WARNING_CFLAGS) noinst_HEADERS = gst-play-kb.h diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am index 97ce95dce3..45623e5507 100644 --- a/playback/player/gtk/Makefile.am +++ b/playback/player/gtk/Makefile.am @@ -28,9 +28,8 @@ BUILT_SOURCES: gtk-play-resources.c gtk-play-resources.h gtk_play_SOURCES = gtk-play.c gtk-play-resources.c gtk-video-renderer.c -LDADD = $(top_builddir)/lib/gst/player/.libs/libgstplayer-@GST_PLAYER_API_VERSION@.la \ - $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) +LDADD = $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) -AM_CFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) $(WARNING_CFLAGS) +AM_CFLAGS = $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) $(WARNING_CFLAGS) noinst_HEADERS = gtk-play-resources.h gtk-video-renderer.h diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 51d0bf0235..c91693b78f 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -24,7 +24,6 @@ #include #include -#include #include #include diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj index 9a3e45cf45..6d9ff1f322 100644 --- a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -23,8 +23,6 @@ AD2B885C198D65470070367B /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD2B885A198D65470070367B /* MainStoryboard_iPad.storyboard */; }; AD2B8861198D65780070367B /* LibraryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B885E198D65780070367B /* LibraryViewController.m */; }; AD2B8862198D65780070367B /* VideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8860198D65780070367B /* VideoViewController.m */; }; - AD2B886C198D69ED0070367B /* gstplayer.c in Sources */ = {isa = PBXBuildFile; fileRef = AD2B886A198D69ED0070367B /* gstplayer.c */; }; - E95DEC9B1B8332F100CC3512 /* gstplayer-media-info.c in Sources */ = {isa = PBXBuildFile; fileRef = E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -53,12 +51,6 @@ AD2B885E198D65780070367B /* LibraryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LibraryViewController.m; sourceTree = ""; }; AD2B885F198D65780070367B /* VideoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoViewController.h; sourceTree = ""; }; AD2B8860198D65780070367B /* VideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoViewController.m; sourceTree = ""; }; - AD2B886A198D69ED0070367B /* gstplayer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gstplayer.c; sourceTree = ""; }; - AD2B886B198D69ED0070367B /* gstplayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gstplayer.h; sourceTree = ""; }; - E95DEC971B8332F100CC3512 /* gstplayer-media-info-private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "gstplayer-media-info-private.h"; sourceTree = ""; }; - E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "gstplayer-media-info.c"; sourceTree = ""; }; - E95DEC991B8332F100CC3512 /* gstplayer-media-info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "gstplayer-media-info.h"; sourceTree = ""; }; - E95DEC9A1B8332F100CC3512 /* player.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = player.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -79,7 +71,6 @@ AD2B8812198D631B0070367B = { isa = PBXGroup; children = ( - AD2B8868198D69ED0070367B /* gst */, AD2B8826198D631B0070367B /* GstPlay */, AD2B881D198D631B0070367B /* Frameworks */, AD2B881C198D631B0070367B /* Products */, @@ -140,28 +131,6 @@ name = "Supporting Files"; sourceTree = ""; }; - AD2B8868198D69ED0070367B /* gst */ = { - isa = PBXGroup; - children = ( - AD2B8869198D69ED0070367B /* player */, - ); - name = gst; - path = ../lib/gst; - sourceTree = ""; - }; - AD2B8869198D69ED0070367B /* player */ = { - isa = PBXGroup; - children = ( - E95DEC971B8332F100CC3512 /* gstplayer-media-info-private.h */, - E95DEC981B8332F100CC3512 /* gstplayer-media-info.c */, - E95DEC991B8332F100CC3512 /* gstplayer-media-info.h */, - E95DEC9A1B8332F100CC3512 /* player.h */, - AD2B886A198D69ED0070367B /* gstplayer.c */, - AD2B886B198D69ED0070367B /* gstplayer.h */, - ); - path = player; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -229,14 +198,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E95DEC9B1B8332F100CC3512 /* gstplayer-media-info.c in Sources */, AD2B8861198D65780070367B /* LibraryViewController.m in Sources */, AD2B8831198D631B0070367B /* AppDelegate.m in Sources */, AD2B8862198D65780070367B /* VideoViewController.m in Sources */, AD2B8858198D637A0070367B /* EaglUIVIew.m in Sources */, AD2B882D198D631B0070367B /* main.m in Sources */, AD2B8837198D631B0070367B /* gst_ios_init.m in Sources */, - AD2B886C198D69ED0070367B /* gstplayer.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/playback/player/ios/GstPlay/VideoViewController.m b/playback/player/ios/GstPlay/VideoViewController.m index 19c379926c..079fcde229 100644 --- a/playback/player/ios/GstPlay/VideoViewController.m +++ b/playback/player/ios/GstPlay/VideoViewController.m @@ -1,5 +1,5 @@ #import "VideoViewController.h" -#import +#import #import @interface VideoViewController () { diff --git a/playback/player/qt/play.pro b/playback/player/qt/play.pro index d16440c284..87a14902d4 100644 --- a/playback/player/qt/play.pro +++ b/playback/player/qt/play.pro @@ -6,8 +6,6 @@ CONFIG += c++11 DEFINES += GST_USE_UNSTABLE_API -INCLUDEPATH += ../lib - RESOURCES += qml.qrc # Additional import path used to resolve QML modules in Qt Creator's code model @@ -22,11 +20,8 @@ QT_CONFIG -= no-pkg-config CONFIG += link_pkgconfig PKGCONFIG = \ gstreamer-1.0 \ - gstreamer-audio-1.0 \ - gstreamer-tag-1.0 \ - gstreamer-pbutils-1.0 \ - gstreamer-video-1.0 \ - gstreamer-gl-1.0 + gstreamer-player-1.0 \ + gstreamer-tag-1.0 } macx { @@ -46,8 +41,6 @@ HEADERS += \ SOURCES += main.cpp \ qgstplayer.cpp \ - ../lib/gst/player/gstplayer.c \ - ../lib/gst/player/gstplayer-media-info.c \ player.cpp \ quickrenderer.cpp \ imagesample.cpp From 048de744414101463d632ac8488a9dcf63dee33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 7 Jan 2016 00:22:26 +0200 Subject: [PATCH 161/412] playback/player: android: Register native_stop() with JNI https://github.com/sdroege/gst-player/issues/143 --- playback/player/android/jni/player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c index ed72e86f75..58d177ebc1 100644 --- a/playback/player/android/jni/player.c +++ b/playback/player/android/jni/player.c @@ -450,6 +450,7 @@ static JNINativeMethod native_methods[] = { {"nativeNew", "()V", (void *) native_new}, {"nativePlay", "()V", (void *) native_play}, {"nativePause", "()V", (void *) native_pause}, + {"nativeStop", "()V", (void *) native_stop}, {"nativeSeek", "(J)V", (void *) native_seek}, {"nativeFree", "()V", (void *) native_free}, {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri}, From e8906735ae3dc669e25bf62459f9c7206ed06abe Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 5 Feb 2016 08:45:44 +0530 Subject: [PATCH 162/412] playback/player: android: Move to gradle based build --- playback/player/android/Makefile.am | 23 +++-- playback/player/android/app/build.gradle | 73 +++++++++++++++ .../player/android/app/proguard-rules.pro | 17 ++++ .../{ => app/src/main}/AndroidManifest.xml | 10 +- .../org/freedesktop/gstreamer/Player.java | 0 .../player/GStreamerSurfaceView.java | 0 .../freedesktop/gstreamer/player/Play.java | 0 .../android/{ => app/src/main}/jni/Android.mk | 0 .../android/app/src/main/jni/Application.mk | 1 + .../android/{ => app/src/main}/jni/player.c | 0 .../{ => app/src/main}/res/layout/main.xml | 0 .../{ => app/src/main}/res/values/strings.xml | 0 playback/player/android/build.gradle | 23 +++++ playback/player/android/build.xml | 92 ------------------- playback/player/android/gradle.properties | 18 ++++ playback/player/android/settings.gradle | 1 + 16 files changed, 150 insertions(+), 108 deletions(-) create mode 100644 playback/player/android/app/build.gradle create mode 100644 playback/player/android/app/proguard-rules.pro rename playback/player/android/{ => app/src/main}/AndroidManifest.xml (98%) rename playback/player/android/{src => app/src/main/java}/org/freedesktop/gstreamer/Player.java (100%) rename playback/player/android/{src => app/src/main/java}/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java (100%) rename playback/player/android/{src => app/src/main/java}/org/freedesktop/gstreamer/player/Play.java (100%) rename playback/player/android/{ => app/src/main}/jni/Android.mk (100%) create mode 100644 playback/player/android/app/src/main/jni/Application.mk rename playback/player/android/{ => app/src/main}/jni/player.c (100%) rename playback/player/android/{ => app/src/main}/res/layout/main.xml (100%) rename playback/player/android/{ => app/src/main}/res/values/strings.xml (100%) create mode 100644 playback/player/android/build.gradle delete mode 100644 playback/player/android/build.xml create mode 100644 playback/player/android/gradle.properties create mode 100644 playback/player/android/settings.gradle diff --git a/playback/player/android/Makefile.am b/playback/player/android/Makefile.am index 2664abbab9..27d8ada304 100644 --- a/playback/player/android/Makefile.am +++ b/playback/player/android/Makefile.am @@ -1,10 +1,15 @@ EXTRA_DIST = \ - AndroidManifest.xml \ - jni/Android.mk \ - jni/player.c \ - res/layout/main.xml \ - res/values/strings.xml \ - src/org/freedesktop/gstreamer/Player.java \ - src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ - src/org/freedesktop/gstreamer/player/Play.java - + build.gradle \ + gradle.properties \ + settings.gradle \ + app/build.gradle \ + app/proguard-rules.pro \ + app/src/main/AndroidManifest.xml \ + app/src/main/java/org/freedesktop/gstreamer/Player.java \ + app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ + app/src/main/java/org/freedesktop/gstreamer/player/Play.java \ + app/src/main/jni/Android.mk \ + app/src/main/jni/Application.mk \ + app/src/main/jni/player.c \ + app/src/main/res/layout/main.xml \ + app/src/main/res/values/strings.xml diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle new file mode 100644 index 0000000000..e908022458 --- /dev/null +++ b/playback/player/android/app/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + + +def getNdkCommandLine(ndkRoot, target) { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.properties['user.home'] + '/cerbero/dist/android_arm' + + if (ndkRoot == null) + throw new GradleException('NDK not configured') + + return ["$ndkRoot/ndk-build", + 'NDK_PROJECT_PATH=build', + 'APP_BUILD_SCRIPT=src/main/jni/Android.mk', + 'NDK_APPLICATION_MK=src/main/jni/Application.mk', + 'GSTREAMER_JAVA_SRC_DIR=src/main/java', + "GSTREAMER_ROOT_ANDROID=$gstRoot", + target] +} + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + sourceSets { + main { + // Avoid using the built in JNI generation plugin + jni.srcDirs = [] + jniLibs.srcDirs = ['build/libs'] + } + } + + defaultConfig { + applicationId "org.freedesktop.gstreamer.play" + minSdkVersion 15 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + // Before compiling our app, prepare NDK code + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn ndkBuild + } + + // Need to call clean on NDK ourselves too + clean.dependsOn 'ndkClean' + + // Build native code using mk files like on Eclipse + task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') { + commandLine getNdkCommandLine(android.ndkDirectory, 'all') + } + + task ndkClean(type: Exec, description: 'Clean JNI code built via NDK') { + commandLine getNdkCommandLine(android.ndkDirectory, 'clean') + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/playback/player/android/app/proguard-rules.pro b/playback/player/android/app/proguard-rules.pro new file mode 100644 index 0000000000..d5d45de3e2 --- /dev/null +++ b/playback/player/android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/arun/code/android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/app/src/main/AndroidManifest.xml similarity index 98% rename from playback/player/android/AndroidManifest.xml rename to playback/player/android/app/src/main/AndroidManifest.xml index 41d56522a2..9823f22709 100644 --- a/playback/player/android/AndroidManifest.xml +++ b/playback/player/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,12 @@ - + package="org.freedesktop.gstreamer.play"> + + @@ -174,7 +173,6 @@ - @@ -341,8 +339,6 @@ - - diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java similarity index 100% rename from playback/player/android/src/org/freedesktop/gstreamer/Player.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java similarity index 100% rename from playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java similarity index 100% rename from playback/player/android/src/org/freedesktop/gstreamer/player/Play.java rename to playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/app/src/main/jni/Android.mk similarity index 100% rename from playback/player/android/jni/Android.mk rename to playback/player/android/app/src/main/jni/Android.mk diff --git a/playback/player/android/app/src/main/jni/Application.mk b/playback/player/android/app/src/main/jni/Application.mk new file mode 100644 index 0000000000..87c0990478 --- /dev/null +++ b/playback/player/android/app/src/main/jni/Application.mk @@ -0,0 +1 @@ +APP_PLATFORM = 15 diff --git a/playback/player/android/jni/player.c b/playback/player/android/app/src/main/jni/player.c similarity index 100% rename from playback/player/android/jni/player.c rename to playback/player/android/app/src/main/jni/player.c diff --git a/playback/player/android/res/layout/main.xml b/playback/player/android/app/src/main/res/layout/main.xml similarity index 100% rename from playback/player/android/res/layout/main.xml rename to playback/player/android/app/src/main/res/layout/main.xml diff --git a/playback/player/android/res/values/strings.xml b/playback/player/android/app/src/main/res/values/strings.xml similarity index 100% rename from playback/player/android/res/values/strings.xml rename to playback/player/android/app/src/main/res/values/strings.xml diff --git a/playback/player/android/build.gradle b/playback/player/android/build.gradle new file mode 100644 index 0000000000..e0b366a781 --- /dev/null +++ b/playback/player/android/build.gradle @@ -0,0 +1,23 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/playback/player/android/build.xml b/playback/player/android/build.xml deleted file mode 100644 index 4ef18c8f98..0000000000 --- a/playback/player/android/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/playback/player/android/gradle.properties b/playback/player/android/gradle.properties new file mode 100644 index 0000000000..1d3591c8a4 --- /dev/null +++ b/playback/player/android/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/playback/player/android/settings.gradle b/playback/player/android/settings.gradle new file mode 100644 index 0000000000..e7b4def49c --- /dev/null +++ b/playback/player/android/settings.gradle @@ -0,0 +1 @@ +include ':app' From b46d26b81f7f2be95bad2738b652ca7a3b513d53 Mon Sep 17 00:00:00 2001 From: Mariusz Wasak Date: Wed, 16 Mar 2016 15:45:17 +0100 Subject: [PATCH 163/412] playback/player: gtk: Change True to glib TRUE to make gtk player project compile https://github.com/sdroege/gst-player/pull/149 --- playback/player/gtk/gtk-play.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index c91693b78f..6bc8fd8ad7 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1060,7 +1060,7 @@ create_visualization_menu (GtkPlay * play) item = gtk_radio_menu_item_new_with_label (group, label); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); if (g_strcmp0 (label, cur_vis) == 0) - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); g_object_set_data_full (G_OBJECT (item), "name", label, (GDestroyNotify) g_free); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); @@ -1075,7 +1075,7 @@ create_visualization_menu (GtkPlay * play) group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); g_object_set_data (G_OBJECT (item), "name", (gpointer) "disable"); if (cur_vis == NULL) - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (visualization_changed_cb), play); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); @@ -1130,7 +1130,7 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (index)); g_object_set_data (G_OBJECT (item), "type", GSIZE_TO_POINTER (type)); if (current_index == index) - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); g_free (buffer); g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (track_changed_cb), play); @@ -1143,7 +1143,7 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (-1)); g_object_set_data (G_OBJECT (item), "type", GSIZE_TO_POINTER (type)); if (current_index == -1) - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), True); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (track_changed_cb), play); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); From edc47343838416f23cf0c0fa3aa8007d2b42ffcb Mon Sep 17 00:00:00 2001 From: Mariusz Wasak Date: Tue, 22 Mar 2016 15:45:36 +0100 Subject: [PATCH 164/412] playback/player: Changing icons names to make it visible on gtk player Fixes https://github.com/sdroege/gst-player/pull/150 --- playback/player/gtk/resources/toolbar.ui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/playback/player/gtk/resources/toolbar.ui b/playback/player/gtk/resources/toolbar.ui index c185795c11..f71e2f2fa2 100644 --- a/playback/player/gtk/resources/toolbar.ui +++ b/playback/player/gtk/resources/toolbar.ui @@ -7,7 +7,7 @@ True False 30 - gtk-media-forward + media-seek-forward 0
@@ -21,7 +21,7 @@ True False 30 - gtk-media-next + media-skip-forward 0 @@ -37,14 +37,14 @@ 0 0 35 - gtk-media-play + media-playback-start 0 True False 30 - gtk-media-previous + media-skip-backward restore_image @@ -58,7 +58,7 @@ True False 30 - gtk-media-rewind + media-seek-backward toolbar From 9c64ebb048a9407bd4bbc9ee87e1ad037dcc7307 Mon Sep 17 00:00:00 2001 From: Emmanuel Imbernon Date: Mon, 28 Mar 2016 17:09:59 +0200 Subject: [PATCH 165/412] playback/player: ios: Sync gst_ios_init.[mh] with latest version Fixes https://github.com/sdroege/gst-player/pull/154 --- playback/player/ios/GstPlay/gst_ios_init.h | 2 +- playback/player/ios/GstPlay/gst_ios_init.m | 104 +++++++++++++++++---- 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/playback/player/ios/GstPlay/gst_ios_init.h b/playback/player/ios/GstPlay/gst_ios_init.h index a880bf6363..870515bd87 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.h +++ b/playback/player/ios/GstPlay/gst_ios_init.h @@ -15,6 +15,7 @@ G_PASTE(g_io_module_, G_PASTE(name, _load_static)) () * You can also enable individual plugins. See gst_ios_init.c to see their names */ +//#define GST_IOS_PLUGINS_GES #define GST_IOS_PLUGINS_CORE //#define GST_IOS_PLUGINS_CAPTURE #define GST_IOS_PLUGINS_CODECS_RESTRICTED @@ -27,7 +28,6 @@ G_PASTE(g_io_module_, G_PASTE(name, _load_static)) () #define GST_IOS_PLUGINS_EFFECTS #define GST_IOS_PLUGINS_CODECS #define GST_IOS_PLUGINS_NET -//#define GST_IOS_PLUGINS_EDITING #define GST_IOS_GIO_MODULE_GNUTLS diff --git a/playback/player/ios/GstPlay/gst_ios_init.m b/playback/player/ios/GstPlay/gst_ios_init.m index 403f9f9c17..214b2e8bc3 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.m +++ b/playback/player/ios/GstPlay/gst_ios_init.m @@ -1,5 +1,8 @@ #include "gst_ios_init.h" +#if defined(GST_IOS_PLUGIN_NLE) || defined(GST_IOS_PLUGINS_GES) +GST_PLUGIN_STATIC_DECLARE(nle); +#endif #if defined(GST_IOS_PLUGIN_COREELEMENTS) || defined(GST_IOS_PLUGINS_CORE) GST_PLUGIN_STATIC_DECLARE(coreelements); #endif @@ -111,6 +114,12 @@ GST_PLUGIN_STATIC_DECLARE(realmedia); #if defined(GST_IOS_PLUGIN_X264) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_DECLARE(x264); #endif +#if defined(GST_IOS_PLUGIN_LAME) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(lame); +#endif +#if defined(GST_IOS_PLUGIN_MPG123) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) +GST_PLUGIN_STATIC_DECLARE(mpg123); +#endif #if defined(GST_IOS_PLUGIN_LIBAV) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_DECLARE(libav); #endif @@ -255,15 +264,15 @@ GST_PLUGIN_STATIC_DECLARE(gaudieffects); #if defined(GST_IOS_PLUGIN_GEOMETRICTRANSFORM) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(geometrictransform); #endif +#if defined(GST_IOS_PLUGIN_INTER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(inter); +#endif #if defined(GST_IOS_PLUGIN_INTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(interlace); #endif #if defined(GST_IOS_PLUGIN_IVTC) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(ivtc); #endif -#if defined(GST_IOS_PLUGIN_LIVEADDER) || defined(GST_IOS_PLUGINS_EFFECTS) -GST_PLUGIN_STATIC_DECLARE(liveadder); -#endif #if defined(GST_IOS_PLUGIN_RAWPARSE) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(rawparse); #endif @@ -279,9 +288,18 @@ GST_PLUGIN_STATIC_DECLARE(smooth); #if defined(GST_IOS_PLUGIN_SPEED) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(speed); #endif +#if defined(GST_IOS_PLUGIN_SOUNDTOUCH) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(soundtouch); +#endif #if defined(GST_IOS_PLUGIN_VIDEOFILTERSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_DECLARE(videofiltersbad); #endif +#if defined(GST_IOS_PLUGIN_AUDIOMIXER) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(audiomixer); +#endif +#if defined(GST_IOS_PLUGIN_COMPOSITOR) || defined(GST_IOS_PLUGINS_EFFECTS) +GST_PLUGIN_STATIC_DECLARE(compositor); +#endif #if defined(GST_IOS_PLUGIN_SUBPARSE) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(subparse); #endif @@ -294,6 +312,9 @@ GST_PLUGIN_STATIC_DECLARE(theora); #if defined(GST_IOS_PLUGIN_VORBIS) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(vorbis); #endif +#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(opus); +#endif #if defined(GST_IOS_PLUGIN_IVORBISDEC) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(ivorbisdec); #endif @@ -384,8 +405,8 @@ GST_PLUGIN_STATIC_DECLARE(dvbsuboverlay); #if defined(GST_IOS_PLUGIN_DVDSPU) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(dvdspu); #endif -#if defined(GST_IOS_PLUGIN_FRAGMENTED) || defined(GST_IOS_PLUGINS_CODECS) -GST_PLUGIN_STATIC_DECLARE(fragmented); +#if defined(GST_IOS_PLUGIN_HLS) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(hls); #endif #if defined(GST_IOS_PLUGIN_ID3TAG) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(id3tag); @@ -399,8 +420,11 @@ GST_PLUGIN_STATIC_DECLARE(midi); #if defined(GST_IOS_PLUGIN_MXF) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(mxf); #endif -#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) -GST_PLUGIN_STATIC_DECLARE(opus); +#if defined(GST_IOS_PLUGIN_OPENH264) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(openh264); +#endif +#if defined(GST_IOS_PLUGIN_OPUSPARSE) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(opusparse); #endif #if defined(GST_IOS_PLUGIN_PCAPPARSE) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(pcapparse); @@ -438,6 +462,12 @@ GST_PLUGIN_STATIC_DECLARE(gdp); #if defined(GST_IOS_PLUGIN_RSVG) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(rsvg); #endif +#if defined(GST_IOS_PLUGIN_OPENJPEG) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(openjpeg); +#endif +#if defined(GST_IOS_PLUGIN_SPANDSP) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(spandsp); +#endif #if defined(GST_IOS_PLUGIN_TCP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_DECLARE(tcp); #endif @@ -462,8 +492,11 @@ GST_PLUGIN_STATIC_DECLARE(dataurisrc); #if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_DECLARE(sdp); #endif -#if defined(GST_IOS_PLUGIN_GNONLIN) || defined(GST_IOS_PLUGINS_EDITING) -GST_PLUGIN_STATIC_DECLARE(gnonlin); +#if defined(GST_IOS_PLUGIN_SRTP) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(srtp); +#endif +#if defined(GST_IOS_PLUGIN_RTSPCLIENTSINK) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(rtspclientsink); #endif #if defined(GST_IOS_GIO_MODULE_GNUTLS) @@ -506,7 +539,10 @@ gst_ios_init (void) gst_init (NULL, NULL); - #if defined(GST_IOS_PLUGIN_COREELEMENTS) || defined(GST_IOS_PLUGINS_CORE) + #if defined(GST_IOS_PLUGIN_NLE) || defined(GST_IOS_PLUGINS_GES) + GST_PLUGIN_STATIC_REGISTER(nle); +#endif +#if defined(GST_IOS_PLUGIN_COREELEMENTS) || defined(GST_IOS_PLUGINS_CORE) GST_PLUGIN_STATIC_REGISTER(coreelements); #endif #if defined(GST_IOS_PLUGIN_ADDER) || defined(GST_IOS_PLUGINS_CORE) @@ -617,6 +653,12 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_X264) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_REGISTER(x264); #endif +#if defined(GST_IOS_PLUGIN_LAME) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(lame); +#endif +#if defined(GST_IOS_PLUGIN_MPG123) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) + GST_PLUGIN_STATIC_REGISTER(mpg123); +#endif #if defined(GST_IOS_PLUGIN_LIBAV) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_REGISTER(libav); #endif @@ -761,15 +803,15 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_GEOMETRICTRANSFORM) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(geometrictransform); #endif +#if defined(GST_IOS_PLUGIN_INTER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(inter); +#endif #if defined(GST_IOS_PLUGIN_INTERLACE) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(interlace); #endif #if defined(GST_IOS_PLUGIN_IVTC) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(ivtc); #endif -#if defined(GST_IOS_PLUGIN_LIVEADDER) || defined(GST_IOS_PLUGINS_EFFECTS) - GST_PLUGIN_STATIC_REGISTER(liveadder); -#endif #if defined(GST_IOS_PLUGIN_RAWPARSE) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(rawparse); #endif @@ -785,9 +827,18 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_SPEED) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(speed); #endif +#if defined(GST_IOS_PLUGIN_SOUNDTOUCH) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(soundtouch); +#endif #if defined(GST_IOS_PLUGIN_VIDEOFILTERSBAD) || defined(GST_IOS_PLUGINS_EFFECTS) GST_PLUGIN_STATIC_REGISTER(videofiltersbad); #endif +#if defined(GST_IOS_PLUGIN_AUDIOMIXER) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(audiomixer); +#endif +#if defined(GST_IOS_PLUGIN_COMPOSITOR) || defined(GST_IOS_PLUGINS_EFFECTS) + GST_PLUGIN_STATIC_REGISTER(compositor); +#endif #if defined(GST_IOS_PLUGIN_SUBPARSE) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(subparse); #endif @@ -800,6 +851,9 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_VORBIS) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(vorbis); #endif +#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(opus); +#endif #if defined(GST_IOS_PLUGIN_IVORBISDEC) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(ivorbisdec); #endif @@ -890,8 +944,8 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_DVDSPU) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(dvdspu); #endif -#if defined(GST_IOS_PLUGIN_FRAGMENTED) || defined(GST_IOS_PLUGINS_CODECS) - GST_PLUGIN_STATIC_REGISTER(fragmented); +#if defined(GST_IOS_PLUGIN_HLS) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(hls); #endif #if defined(GST_IOS_PLUGIN_ID3TAG) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(id3tag); @@ -905,8 +959,11 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_MXF) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(mxf); #endif -#if defined(GST_IOS_PLUGIN_OPUS) || defined(GST_IOS_PLUGINS_CODECS) - GST_PLUGIN_STATIC_REGISTER(opus); +#if defined(GST_IOS_PLUGIN_OPENH264) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(openh264); +#endif +#if defined(GST_IOS_PLUGIN_OPUSPARSE) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(opusparse); #endif #if defined(GST_IOS_PLUGIN_PCAPPARSE) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(pcapparse); @@ -944,6 +1001,12 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_RSVG) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(rsvg); #endif +#if defined(GST_IOS_PLUGIN_OPENJPEG) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(openjpeg); +#endif +#if defined(GST_IOS_PLUGIN_SPANDSP) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(spandsp); +#endif #if defined(GST_IOS_PLUGIN_TCP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_REGISTER(tcp); #endif @@ -968,8 +1031,11 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_REGISTER(sdp); #endif -#if defined(GST_IOS_PLUGIN_GNONLIN) || defined(GST_IOS_PLUGINS_EDITING) - GST_PLUGIN_STATIC_REGISTER(gnonlin); +#if defined(GST_IOS_PLUGIN_SRTP) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(srtp); +#endif +#if defined(GST_IOS_PLUGIN_RTSPCLIENTSINK) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(rtspclientsink); #endif #if defined(GST_IOS_GIO_MODULE_GNUTLS) From 2435b0e37c754f0ad4d122acf83de2d2084110af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 10 May 2016 22:01:54 +0300 Subject: [PATCH 166/412] playback/player: android: Update build tools version and ship gradle wrapper script --- playback/player/android/app/build.gradle | 4 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53637 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + playback/player/android/gradlew | 160 ++++++++++++++++++ playback/player/android/gradlew.bat | 90 ++++++++++ 5 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 playback/player/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 playback/player/android/gradlew create mode 100644 playback/player/android/gradlew.bat diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index e908022458..50b135e94e 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -23,7 +23,7 @@ def getNdkCommandLine(ndkRoot, target) { android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" sourceSets { main { @@ -36,7 +36,7 @@ android { defaultConfig { applicationId "org.freedesktop.gstreamer.play" minSdkVersion 15 - targetSdkVersion 22 + targetSdkVersion 15 versionCode 1 versionName "1.0" } diff --git a/playback/player/android/gradle/wrapper/gradle-wrapper.jar b/playback/player/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..05ef575b0cd0173fc735f2857ce4bd594ce4f6bd GIT binary patch literal 53637 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 t9m@SZ!E*7&jsr?!t7TL-WYI4eM@gAug8 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/playback/player/android/gradlew.bat b/playback/player/android/gradlew.bat new file mode 100644 index 0000000000..aec99730b4 --- /dev/null +++ b/playback/player/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 10dd3a21776d88394c06d9725993a4673d467756 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 20 Jul 2016 11:43:51 +0200 Subject: [PATCH 167/412] playback/player: Call gst_deinit() in all applications at the end of main() Needed to be able to track leaks using valgrind or the leaks tracer. --- playback/player/gst-play/gst-play.c | 1 + playback/player/gtk/gtk-play.c | 1 + playback/player/qt/main.cpp | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 46f6d62a71..bd21bd27af 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -768,5 +768,6 @@ main (int argc, char **argv) play_free (play); g_print ("\n"); + gst_deinit (); return 0; } diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 6bc8fd8ad7..7f9e588cb1 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1910,5 +1910,6 @@ main (gint argc, gchar ** argv) status = g_application_run (G_APPLICATION (app), argc, argv);; g_object_unref (app); + gst_deinit (); return status; } diff --git a/playback/player/qt/main.cpp b/playback/player/qt/main.cpp index 4ef6b64f79..9a8adf6150 100644 --- a/playback/player/qt/main.cpp +++ b/playback/player/qt/main.cpp @@ -30,6 +30,7 @@ int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + int result; QCommandLineParser parser; parser.setApplicationDescription("GstPlayer"); @@ -70,5 +71,8 @@ int main(int argc, char *argv[]) if (!media_files.isEmpty()) player->setPlaylist(media_files); - return app.exec(); + result = app.exec(); + + gst_deinit (); + return result; } From 5b8ba60c46f66cb31313c516af17aed374c95465 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 20 Jul 2016 12:38:51 +0200 Subject: [PATCH 168/412] playback/player: gtk: fix gtkglsink leak The ref returned by gst_element_factory_make() was leaked. --- playback/player/gtk/gtk-video-renderer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index b34f927370..c62af1cd64 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -114,12 +114,13 @@ gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) } else { gtk_sink = gst_element_factory_make ("gtksink", NULL); - self->sink = gtk_sink; + self->sink = gst_object_ref (gtk_sink); } g_assert (self->sink != NULL); g_object_get (gtk_sink, "widget", &self->widget, NULL); + gst_object_unref (gtk_sink); } static GstElement *gst_player_gtk_video_renderer_create_video_sink From f8d4796a57bb28a2089791cb7b5c7d10427bfc63 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 20 Jul 2016 16:20:40 +0200 Subject: [PATCH 169/412] playback/player: gtk: fix widget leak The ref returned by gst_player_gtk_video_renderer_get_widget() was never released. --- playback/player/gtk/gtk-play.c | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 7f9e588cb1..a520bef765 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1775,6 +1775,7 @@ gtk_play_dispose (GObject * object) g_object_unref (self->player); } self->player = NULL; + g_clear_object (&self->video_area); G_OBJECT_CLASS (gtk_play_parent_class)->dispose (object); } From e82333ab704419307fa1a3703f492e4f04b51571 Mon Sep 17 00:00:00 2001 From: "Maxin B. John" Date: Fri, 5 Aug 2016 17:52:18 +0300 Subject: [PATCH 170/412] playback/player: gtk-play: provide similar behaviour for quit and close In x86 targets, gtk-play just pause rather than quitting the application when we click the close button (delete-event). Change the callback function to get similar behaviour when we click on "Quit" menu option. --- playback/player/gtk/gtk-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index a520bef765..16afc6b953 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -177,7 +177,7 @@ load_from_builder (const gchar * filename, gboolean register_sig_handler, static void delete_event_cb (GtkWidget * widget, GdkEvent * event, GtkPlay * play) { - gst_player_stop (play->player); + gtk_widget_destroy (GTK_WIDGET (play)); } static void From 89c74b3dd86000581d15270dddbee1f6a2cade39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 18 Aug 2016 15:48:57 +0300 Subject: [PATCH 171/412] playback/player: gtk: Use GQueue instead of g_list_append() or the prepend-reverse trick --- playback/player/gtk/gtk-play.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 16afc6b953..8aa6975c53 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -544,7 +544,7 @@ static GList * open_file_dialog (GtkPlay * play, gboolean multi) { int res; - GList *uris = NULL; + GQueue uris = G_QUEUE_INIT; GtkWidget *chooser; GtkWidget *parent; @@ -568,7 +568,7 @@ open_file_dialog (GtkPlay * play, gboolean multi) l = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (chooser)); while (l) { - uris = g_list_append (uris, l->data); + g_queue_push_tail (&uris, l->data); l = g_slist_delete_link (l, l); } } @@ -577,7 +577,7 @@ open_file_dialog (GtkPlay * play, gboolean multi) if (!play) gtk_widget_destroy (parent); - return uris; + return uris.head; } static void @@ -1828,16 +1828,15 @@ gtk_play_app_command_line (GApplication * application, if (uris_array) { gchar **p; + GQueue uris_builder = G_QUEUE_INIT; p = uris_array; while (*p) { - uris = - g_list_prepend (uris, - gst_uri_is_valid (*p) ? + g_queue_push_tail (&uris_builder, gst_uri_is_valid (*p) ? g_strdup (*p) : gst_filename_to_uri (*p, NULL)); p++; } - uris = g_list_reverse (uris); + uris = uris_builder.head; } else { uris = open_file_dialog (NULL, TRUE); } From 1d760d92ed6076003112f0653a27e26f11912c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 22 Aug 2016 16:19:59 +0300 Subject: [PATCH 172/412] playback/player: gtk: Block value-change signal handler while updating the range of the seekbar too I.e. when updating the duration. Changing the range might also change the value, which would then trigger a seek. --- playback/player/gtk/gtk-play.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8aa6975c53..6dac913583 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1504,8 +1504,12 @@ create_ui (GtkPlay * play) static void duration_changed_cb (GstPlayer * unused, GstClockTime duration, GtkPlay * play) { + g_signal_handlers_block_by_func (play->seekbar, + seekbar_value_changed_cb, play); gtk_range_set_range (GTK_RANGE (play->seekbar), 0.0, (gdouble) duration / GST_SECOND); + g_signal_handlers_unblock_by_func (play->seekbar, + seekbar_value_changed_cb, play); } static void From 5743b39d4e5a11e2fdc2402b14d3032ebbc15fc0 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 27 Jul 2016 21:10:31 +0530 Subject: [PATCH 173/412] playback/player: android: Update build tools and gradle New build tools needed for Instant Run, and Android Studio wants us to update that and the gradle version. --- playback/player/android/build.gradle | 2 +- .../player/android/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/playback/player/android/build.gradle b/playback/player/android/build.gradle index e0b366a781..77ce66ea36 100644 --- a/playback/player/android/build.gradle +++ b/playback/player/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/playback/player/android/gradle/wrapper/gradle-wrapper.properties b/playback/player/android/gradle/wrapper/gradle-wrapper.properties index 428c55e575..fc500fd545 100644 --- a/playback/player/android/gradle/wrapper/gradle-wrapper.properties +++ b/playback/player/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 10 21:58:01 EEST 2016 +#Tue Aug 23 11:07:06 IST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From d30c65f9f8e4df372794a6007cfd1bea9f16c776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Aug 2016 19:02:53 +0300 Subject: [PATCH 174/412] playback/player: Block value-changed signal when playing an URI to prevent spurious seeks --- playback/player/gtk/gtk-play.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 6dac913583..3ff03cd5bc 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -409,7 +409,11 @@ static void play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) { /* reset the button/widget state to default */ + g_signal_handlers_block_by_func (play->seekbar, + seekbar_value_changed_cb, play); gtk_range_set_range (GTK_RANGE (play->seekbar), 0, 0); + g_signal_handlers_unblock_by_func (play->seekbar, + seekbar_value_changed_cb, play); gtk_widget_set_sensitive (play->prev_button, g_list_previous (uri) != NULL); gtk_widget_set_sensitive (play->next_button, g_list_next (uri) != NULL); gtk_label_set_label (play->rate_label, NULL); From f6cf0dd99701be7f3632c3e7c99c65eb404df018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Aug 2016 23:48:26 +0300 Subject: [PATCH 175/412] playback/player: gtk: Remove unused variable --- playback/player/gtk/gtk-play.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 3ff03cd5bc..8ae0fea01c 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -60,7 +60,6 @@ typedef struct GstPlayer *player; GstPlayerVideoRenderer *renderer; - gchar *uri; GList *uris; GList *current_uri; @@ -1771,10 +1770,6 @@ gtk_play_dispose (GObject * object) self->inhibit_cookie); self->inhibit_cookie = 0; - if (self->uri) - g_free (self->uri); - self->uri = NULL; - if (self->uris) g_list_free_full (self->uris, g_free); self->uris = NULL; From 3e67a2315c10ed63e73585c112d14e257d4ded05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 26 Aug 2016 18:28:10 +0300 Subject: [PATCH 176/412] Initial commit with empty meson.build --- COPYING | 502 ++++++++++++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 + 2 files changed, 503 insertions(+) create mode 100644 COPYING create mode 100644 meson.build diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..4362b49151 --- /dev/null +++ b/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..e68f76f3e0 --- /dev/null +++ b/meson.build @@ -0,0 +1 @@ +project('gst-examples', 'c', license : 'LGPL') From fc41dd802f499ab4fc73c720a2f35803b5b995a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 26 Aug 2016 19:02:24 +0300 Subject: [PATCH 177/412] Add meson build system for playback/player/{gst-play,gtk} and network/http-launch --- meson.build | 2 ++ network/http-launch/meson.build | 3 +++ network/meson.build | 1 + playback/meson.build | 1 + playback/player/android/Makefile.am | 15 ------------ playback/player/gst-play/Makefile.am | 9 ------- playback/player/gst-play/meson.build | 12 ++++++++++ playback/player/gtk/Makefile.am | 35 ---------------------------- playback/player/gtk/meson.build | 22 +++++++++++++++++ playback/player/ios/Makefile.am | 22 ----------------- playback/player/meson.build | 2 ++ 11 files changed, 43 insertions(+), 81 deletions(-) create mode 100644 network/http-launch/meson.build create mode 100644 network/meson.build create mode 100644 playback/meson.build delete mode 100644 playback/player/android/Makefile.am delete mode 100644 playback/player/gst-play/Makefile.am create mode 100644 playback/player/gst-play/meson.build delete mode 100644 playback/player/gtk/Makefile.am create mode 100644 playback/player/gtk/meson.build delete mode 100644 playback/player/ios/Makefile.am create mode 100644 playback/player/meson.build diff --git a/meson.build b/meson.build index e68f76f3e0..866679ccba 100644 --- a/meson.build +++ b/meson.build @@ -1 +1,3 @@ project('gst-examples', 'c', license : 'LGPL') +subdir('playback') +subdir('network') diff --git a/network/http-launch/meson.build b/network/http-launch/meson.build new file mode 100644 index 0000000000..f9ebc697d6 --- /dev/null +++ b/network/http-launch/meson.build @@ -0,0 +1,3 @@ +gst_dep = dependency('gstreamer-1.0') +gio_dep = dependency('gio-2.0') +executable('http-launch', 'http-launch.c', dependencies : [gst_dep, gio_dep]) diff --git a/network/meson.build b/network/meson.build new file mode 100644 index 0000000000..d53f422d2e --- /dev/null +++ b/network/meson.build @@ -0,0 +1 @@ +subdir('http-launch') diff --git a/playback/meson.build b/playback/meson.build new file mode 100644 index 0000000000..66bf6d5d31 --- /dev/null +++ b/playback/meson.build @@ -0,0 +1 @@ +subdir('player') diff --git a/playback/player/android/Makefile.am b/playback/player/android/Makefile.am deleted file mode 100644 index 27d8ada304..0000000000 --- a/playback/player/android/Makefile.am +++ /dev/null @@ -1,15 +0,0 @@ -EXTRA_DIST = \ - build.gradle \ - gradle.properties \ - settings.gradle \ - app/build.gradle \ - app/proguard-rules.pro \ - app/src/main/AndroidManifest.xml \ - app/src/main/java/org/freedesktop/gstreamer/Player.java \ - app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java \ - app/src/main/java/org/freedesktop/gstreamer/player/Play.java \ - app/src/main/jni/Android.mk \ - app/src/main/jni/Application.mk \ - app/src/main/jni/player.c \ - app/src/main/res/layout/main.xml \ - app/src/main/res/values/strings.xml diff --git a/playback/player/gst-play/Makefile.am b/playback/player/gst-play/Makefile.am deleted file mode 100644 index 184e8c1aa1..0000000000 --- a/playback/player/gst-play/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -bin_PROGRAMS = gst-play - -gst_play_SOURCES = gst-play.c gst-play-kb.c gst-play-kb.h - -LDADD = $(GSTREAMER_LIBS) $(GLIB_LIBS) $(LIBM) - -AM_CFLAGS = $(GSTREAMER_CFLAGS) $(GLIB_CFLAGS) $(WARNING_CFLAGS) - -noinst_HEADERS = gst-play-kb.h diff --git a/playback/player/gst-play/meson.build b/playback/player/gst-play/meson.build new file mode 100644 index 0000000000..719b55b4c9 --- /dev/null +++ b/playback/player/gst-play/meson.build @@ -0,0 +1,12 @@ +gst_dep = dependency('gstreamer-1.0') +gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.7.1.1') + +cc = meson.get_compiler('c') +m_dep = cc.find_library('m', required : true) + +executable('gst-play', + ['gst-play.c', + 'gst-play-kb.c', + 'gst-play-kb.h'], + dependencies : [gst_dep, gstplayer_dep, m_dep]) + diff --git a/playback/player/gtk/Makefile.am b/playback/player/gtk/Makefile.am deleted file mode 100644 index 45623e5507..0000000000 --- a/playback/player/gtk/Makefile.am +++ /dev/null @@ -1,35 +0,0 @@ -bin_PROGRAMS = gtk-play - -gtk-play-resources.c: resources/gresources.xml \ - resources/media_info_dialog.ui \ - resources/toolbar.css \ - resources/toolbar.ui - $(AM_V_GEN) \ - glib-compile-resources \ - --sourcedir=$(srcdir)/resources \ - --target=$@ \ - --generate-source \ - --c-name as \ - $(srcdir)/resources/gresources.xml - -gtk-play-resources.h: resources/gresources.xml \ - resources/media_info_dialog.ui \ - resources/toolbar.css \ - resources/toolbar.ui - $(AM_V_GEN) \ - glib-compile-resources \ - --sourcedir=$(srcdir)/resources \ - --target=$@ \ - --generate-header \ - --c-name as \ - $(srcdir)/resources/gresources.xml - -BUILT_SOURCES: gtk-play-resources.c gtk-play-resources.h - -gtk_play_SOURCES = gtk-play.c gtk-play-resources.c gtk-video-renderer.c - -LDADD = $(GSTREAMER_LIBS) $(GTK_LIBS) $(GTK_X11_LIBS) $(GLIB_LIBS) $(LIBM) $(GMODULE_LIBS) - -AM_CFLAGS = $(GSTREAMER_CFLAGS) $(GTK_CFLAGS) $(GTK_X11_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) $(WARNING_CFLAGS) - -noinst_HEADERS = gtk-play-resources.h gtk-video-renderer.h diff --git a/playback/player/gtk/meson.build b/playback/player/gtk/meson.build new file mode 100644 index 0000000000..08aae4f892 --- /dev/null +++ b/playback/player/gtk/meson.build @@ -0,0 +1,22 @@ +glib_dep = dependency('glib-2.0', version: '>= 2.38') +gobject_dep = dependency('gobject-2.0', version: '>= 2.38') +gmodule_dep = dependency('gmodule-2.0') +gst_dep = dependency('gstreamer-1.0') +gsttag_dep = dependency('gstreamer-tag-1.0') +gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.7.1.1') +gtk_dep = dependency('gtk+-3.0') +x11_dep = dependency('x11', required: false) + +gnome = import('gnome') + +gtk_play_resources = gnome.compile_resources('gtk-play-resources', 'resources/gresources.xml', + source_dir : './resources', + c_name : 'gtk_play') + +executable('gtk-play', + sources: ['gtk-play.c', + gtk_play_resources, + 'gtk-video-renderer.h', + 'gtk-video-renderer.c'], + dependencies : [glib_dep, gobject_dep, gmodule_dep, gst_dep, gsttag_dep, gstplayer_dep, gtk_dep, x11_dep]) + diff --git a/playback/player/ios/Makefile.am b/playback/player/ios/Makefile.am deleted file mode 100644 index c6da505121..0000000000 --- a/playback/player/ios/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -EXTRA_DIST = \ - GstPlay/AppDelegate.h \ - GstPlay/AppDelegate.m \ - GstPlay/EaglUIVIew.h \ - GstPlay/EaglUIVIew.m \ - GstPlay/en.lproj/InfoPlist.strings \ - GstPlay/fonts.conf \ - GstPlay/gst_ios_init.h \ - GstPlay/gst_ios_init.m \ - GstPlay/GstPlay-Info.plist \ - GstPlay/GstPlay-Prefix.pch \ - GstPlay/LibraryViewController.h \ - GstPlay/LibraryViewController.m \ - GstPlay/main.m \ - GstPlay/MainStoryboard_iPad.storyboard \ - GstPlay/MainStoryboard_iPhone.storyboard \ - GstPlay/Ubuntu-R.ttf \ - GstPlay/VideoViewController.h \ - GstPlay/VideoViewController.m \ - GstPlay.xcodeproj/project.xcworkspace/contents.xcworkspacedata \ - GstPlay.xcodeproj/project.pbxproj - diff --git a/playback/player/meson.build b/playback/player/meson.build new file mode 100644 index 0000000000..06877e95d3 --- /dev/null +++ b/playback/player/meson.build @@ -0,0 +1,2 @@ +subdir('gst-play') +subdir('gtk') From a5cdde9119f038a1eb365aca20faa9741a38e788 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Sun, 4 Sep 2016 10:12:52 +0300 Subject: [PATCH 178/412] playback/player/android: Switch to building against universal packages --- playback/player/android/app/build.gradle | 2 +- .../android/app/src/main/jni/Android.mk | 29 +++++++++---------- .../android/app/src/main/jni/Application.mk | 1 + playback/player/android/gradle.properties | 3 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 50b135e94e..5119ca38b8 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -7,7 +7,7 @@ def getNdkCommandLine(ndkRoot, target) { if (project.hasProperty('gstAndroidRoot')) gstRoot = project.gstAndroidRoot else - gstRoot = System.properties['user.home'] + '/cerbero/dist/android_arm' + throw new GradleException('"gstAndroidRoot" must be defined in your gradle properties to the top level directory of the unpacked universal GStreamer Android binaries') if (ndkRoot == null) throw new GradleException('NDK not configured') diff --git a/playback/player/android/app/src/main/jni/Android.mk b/playback/player/android/app/src/main/jni/Android.mk index 65e2044d99..593eaae368 100644 --- a/playback/player/android/app/src/main/jni/Android.mk +++ b/playback/player/android/app/src/main/jni/Android.mk @@ -9,25 +9,22 @@ LOCAL_SHARED_LIBRARIES := gstreamer_android LOCAL_LDLIBS := -llog -landroid include $(BUILD_SHARED_LIBRARY) -ifeq ($(TARGET_ARCH_ABI),armeabi) -GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM) -else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) -GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARMV7) -else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) -GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM64) -else ifeq ($(TARGET_ARCH_ABI),x86) -GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86) -else ifeq ($(TARGET_ARCH_ABI),x86_64) -GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86_64) -else -$(error Target arch ABI not supported) -endif - -ifndef GSTREAMER_ROOT ifndef GSTREAMER_ROOT_ANDROID $(error GSTREAMER_ROOT_ANDROID is not defined!) endif -GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID) + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) endif GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ diff --git a/playback/player/android/app/src/main/jni/Application.mk b/playback/player/android/app/src/main/jni/Application.mk index 87c0990478..9e74937f5a 100644 --- a/playback/player/android/app/src/main/jni/Application.mk +++ b/playback/player/android/app/src/main/jni/Application.mk @@ -1 +1,2 @@ APP_PLATFORM = 15 +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 diff --git a/playback/player/android/gradle.properties b/playback/player/android/gradle.properties index 1d3591c8a4..a2a6ee4bd2 100644 --- a/playback/player/android/gradle.properties +++ b/playback/player/android/gradle.properties @@ -15,4 +15,5 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +gstAndroidRoot=/home/slomo/Projects/gstreamer/head/gst-examples/playback/player/gst-android From 8b0ea8257fdbe63166c0ed532e56d9bbb0bf36ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 3 Oct 2016 13:14:37 +0300 Subject: [PATCH 179/412] playback/player/android: Switch to native gradle ndk-build support Based on a patch by Olivier Crete for another project https://github.com/sdroege/gst-launch-remote/commit/be9470a7e1b0e84b6d9302436b477817a307d23c --- playback/player/android/app/build.gradle | 67 ++++++++---------------- playback/player/android/build.gradle | 2 +- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 5119ca38b8..15d0ad20c0 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -1,44 +1,34 @@ apply plugin: 'com.android.application' - -def getNdkCommandLine(ndkRoot, target) { - def gstRoot - - if (project.hasProperty('gstAndroidRoot')) - gstRoot = project.gstAndroidRoot - else - throw new GradleException('"gstAndroidRoot" must be defined in your gradle properties to the top level directory of the unpacked universal GStreamer Android binaries') - - if (ndkRoot == null) - throw new GradleException('NDK not configured') - - return ["$ndkRoot/ndk-build", - 'NDK_PROJECT_PATH=build', - 'APP_BUILD_SCRIPT=src/main/jni/Android.mk', - 'NDK_APPLICATION_MK=src/main/jni/Application.mk', - 'GSTREAMER_JAVA_SRC_DIR=src/main/java', - "GSTREAMER_ROOT_ANDROID=$gstRoot", - target] -} - android { compileSdkVersion 23 buildToolsVersion "23.0.3" - sourceSets { - main { - // Avoid using the built in JNI generation plugin - jni.srcDirs = [] - jniLibs.srcDirs = ['build/libs'] - } - } - defaultConfig { applicationId "org.freedesktop.gstreamer.play" minSdkVersion 15 targetSdkVersion 15 versionCode 1 versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + throw new GradleException('"gstAndroidRoot" must be defined in your gradle.properties to the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src/main/java", "GSTREAMER_ROOT_ANDROID=$gstRoot" + + targets "gstplayer", "gstreamer_android" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } } buildTypes { @@ -48,21 +38,10 @@ android { } } - // Before compiling our app, prepare NDK code - tasks.withType(JavaCompile) { - compileTask -> compileTask.dependsOn ndkBuild - } - - // Need to call clean on NDK ourselves too - clean.dependsOn 'ndkClean' - - // Build native code using mk files like on Eclipse - task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') { - commandLine getNdkCommandLine(android.ndkDirectory, 'all') - } - - task ndkClean(type: Exec, description: 'Clean JNI code built via NDK') { - commandLine getNdkCommandLine(android.ndkDirectory, 'clean') + externalNativeBuild { + ndkBuild { + path 'src/main/jni/Android.mk' + } } } diff --git a/playback/player/android/build.gradle b/playback/player/android/build.gradle index 77ce66ea36..a3330d4857 100644 --- a/playback/player/android/build.gradle +++ b/playback/player/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 4cd72781af135264deb46fc90d2a3b967cf4869e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 3 Oct 2016 13:15:05 +0300 Subject: [PATCH 180/412] playback/player/android: Add .gitignore --- playback/player/android/.gitignore | 2 ++ playback/player/android/app/.gitignore | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 playback/player/android/.gitignore create mode 100644 playback/player/android/app/.gitignore diff --git a/playback/player/android/.gitignore b/playback/player/android/.gitignore new file mode 100644 index 0000000000..7f6823bcc0 --- /dev/null +++ b/playback/player/android/.gitignore @@ -0,0 +1,2 @@ +.gradle +build/ diff --git a/playback/player/android/app/.gitignore b/playback/player/android/app/.gitignore new file mode 100644 index 0000000000..a6831039e0 --- /dev/null +++ b/playback/player/android/app/.gitignore @@ -0,0 +1,5 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/main/java/org/freedesktop/gstreamer/GStreamer.java +src/main/java/org/freedesktop/gstreamer/androidmedia/ From 0d340e1b2f23cb33985a762a42c54a8d108857dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 1 Nov 2016 18:13:11 +0000 Subject: [PATCH 181/412] meson: update version --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 866679ccba..73c1236dd5 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,3 @@ -project('gst-examples', 'c', license : 'LGPL') +project('gst-examples', 'c', version : '1.11.0.1', license : 'LGPL') subdir('playback') subdir('network') From 0ede8b3796ae35cf786fab0a02aea32ae438313a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 2 Jan 2017 11:12:19 +0530 Subject: [PATCH 182/412] playback/player/android: Force externalNativeBuild before Java building This is needed so that we copy over the Java files from gst-android that include the GStreamer class which is used while compiling app. --- playback/player/android/app/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 15d0ad20c0..68bdd2d456 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -45,6 +45,11 @@ android { } } +afterEvaluate { + compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' From e17b52b5011c1d9e0af771b531399f44302291ce Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 6 Jan 2017 19:00:17 +0530 Subject: [PATCH 183/412] playback/player/android: Fix assets directory Needed with GStreamer <= 1.10 and newer Android Studio so that we correctly place the assets with respect to the rest of the code. --- playback/player/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 68bdd2d456..961c5fb8e5 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -21,7 +21,7 @@ android { else throw new GradleException('"gstAndroidRoot" must be defined in your gradle.properties to the top level directory of the unpacked universal GStreamer Android binaries') - arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src/main/java", "GSTREAMER_ROOT_ANDROID=$gstRoot" + arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src/main/java", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/main/assets" targets "gstplayer", "gstreamer_android" From a8fdbf57b2b7e5a986e0c76986f776bdcf2da9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 13 Jan 2017 12:42:53 +0000 Subject: [PATCH 184/412] meson: bump version --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 73c1236dd5..a033267b9f 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,3 @@ -project('gst-examples', 'c', version : '1.11.0.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.11.1.1', license : 'LGPL') subdir('playback') subdir('network') From 5fc4fd59585d1d669ded8838b4096aa8d5295eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 8 Mar 2017 17:11:12 +0200 Subject: [PATCH 185/412] playback/player/qt: Fix build with new GstPlayer API Configuration has a separate interface now. --- playback/player/qt/main.qml | 1 - playback/player/qt/qgstplayer.cpp | 14 -------------- playback/player/qt/qgstplayer.h | 4 ---- 3 files changed, 19 deletions(-) diff --git a/playback/player/qt/main.qml b/playback/player/qt/main.qml index ce1cf8fb34..c71ffabc0b 100644 --- a/playback/player/qt/main.qml +++ b/playback/player/qt/main.qml @@ -43,7 +43,6 @@ ApplicationWindow { id: player objectName: "player" volume: 0.5 - positionUpdateInterval: 100 autoPlay: false onStateChanged: { diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 77112ef346..5def6fb0fb 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -361,13 +361,6 @@ bool Player::isSubtitleEnabled() const return subtitleEnabled_; } -quint32 Player::positionUpdateInterval() const -{ - Q_ASSERT(player_ != 0); - - return gst_player_get_position_update_interval(player_); -} - void Player::setSubtitleEnabled(bool enabled) { Q_ASSERT(player_ != 0); @@ -379,13 +372,6 @@ void Player::setSubtitleEnabled(bool enabled) emit subtitleEnabledChanged(enabled); } -void Player::setPositionUpdateInterval(quint32 interval) -{ - Q_ASSERT(player_ != 0); - - gst_player_set_position_update_interval(player_, interval); -} - Player::Player(QObject *parent, VideoRenderer *renderer) : QObject(parent) , player_() diff --git a/playback/player/qt/qgstplayer.h b/playback/player/qt/qgstplayer.h index 8ff87d1e8f..088bfabf06 100644 --- a/playback/player/qt/qgstplayer.h +++ b/playback/player/qt/qgstplayer.h @@ -45,8 +45,6 @@ class Player : public QObject Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) Q_PROPERTY(qint64 position READ position NOTIFY positionUpdated) - Q_PROPERTY(quint32 positionUpdateInterval READ positionUpdateInterval - WRITE setPositionUpdateInterval) Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged) Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(int buffering READ buffering NOTIFY bufferingChanged) @@ -92,7 +90,6 @@ public: QVariant currentAudio() const; QVariant currentSubtitle() const; bool isSubtitleEnabled() const; - quint32 positionUpdateInterval() const; bool autoPlay() const; QList playlist() const; @@ -123,7 +120,6 @@ public slots: void setCurrentAudio(QVariant track); void setCurrentSubtitle(QVariant track); void setSubtitleEnabled(bool enabled); - void setPositionUpdateInterval(quint32 interval); void setPlaylist(const QList &playlist); void next(); void previous(); From 71d85d3393b06f0b888cfbb6db3a5b4f3ba1ae8d Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 3 Apr 2017 13:23:50 +0530 Subject: [PATCH 186/412] playback/player/android: Make plugin list in Android.mk easier to read Minor Makefile reformatting to make how users can select what plugins they want easier to figure out. --- playback/player/android/app/src/main/jni/Android.mk | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/playback/player/android/app/src/main/jni/Android.mk b/playback/player/android/app/src/main/jni/Android.mk index 593eaae368..f16d62e311 100644 --- a/playback/player/android/app/src/main/jni/Android.mk +++ b/playback/player/android/app/src/main/jni/Android.mk @@ -30,7 +30,17 @@ endif GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk -GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED) +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) \ + $(GSTREAMER_PLUGINS_PLAYBACK) \ + $(GSTREAMER_PLUGINS_CODECS) \ + $(GSTREAMER_PLUGINS_NET) \ + $(GSTREAMER_PLUGINS_SYS) \ + $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) \ + $(GSTREAMER_CODECS_GPL) \ + $(GSTREAMER_PLUGINS_ENCODING) \ + $(GSTREAMER_PLUGINS_VIS) \ + $(GSTREAMER_PLUGINS_EFFECTS) \ + $(GSTREAMER_PLUGINS_NET_RESTRICTED) GSTREAMER_EXTRA_DEPS := gstreamer-player-1.0 gstreamer-video-1.0 glib-2.0 include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk From 175f7c8da198eeed028caf544cbbb817e1718f92 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 26 Apr 2017 20:08:19 +0200 Subject: [PATCH 187/412] playback/player: qt: use QApplication instead of QGuiApplication This avoids the following fatal error: "QWidget: Cannot create a QWidget without QApplication" https://bugzilla.gnome.org/show_bug.cgi?id=781789 --- playback/player/qt/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playback/player/qt/main.cpp b/playback/player/qt/main.cpp index 9a8adf6150..b4495225cb 100644 --- a/playback/player/qt/main.cpp +++ b/playback/player/qt/main.cpp @@ -29,7 +29,9 @@ int main(int argc, char *argv[]) { - QGuiApplication app(argc, argv); + /* Use QApplication instead of QGuiApplication since the latter is needed + * for widgets like the QFileDialog to work */ + QApplication app(argc, argv); int result; QCommandLineParser parser; From ff1a6546daf51f36f2bda5df4fa1d6fe6fad7dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Fri, 28 Apr 2017 16:25:42 +0200 Subject: [PATCH 188/412] playback/player: don't unref gtk_sink but sink it Commit 5b8ba60c was added because of a memory leak when gtksink was used as renderer. Nonetheless the patch didn't consider two facts: 1\ that the gtk_sink element was in floating state 2\ there is another code path where the gtk_sink element is already sank Thus the correct fix, is not to ref and unref gtk_sink, but sink it. https://bugzilla.gnome.org/show_bug.cgi?id=781904 --- playback/player/gtk/gtk-video-renderer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index c62af1cd64..f07d82403c 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -113,14 +113,12 @@ gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) self->sink = sink; } else { gtk_sink = gst_element_factory_make ("gtksink", NULL); - - self->sink = gst_object_ref (gtk_sink); + self->sink = gst_object_ref_sink (gtk_sink); } g_assert (self->sink != NULL); g_object_get (gtk_sink, "widget", &self->widget, NULL); - gst_object_unref (gtk_sink); } static GstElement *gst_player_gtk_video_renderer_create_video_sink From dbb0375bfef196f7df7b22da6e18422a2dd3250b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Fri, 28 Apr 2017 17:12:25 +0200 Subject: [PATCH 189/412] playback/player: don't unref a contained widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the GTK+ model, when a wiget is created, it is floating, thus when it is added to a widget container, this container is the owner of the widget. The video_area widget is created in two different paths: 1\ when the renderer element is also a GTK+ widget and we are the owners. 2\ when the renderer element is an overlay an video area a new widget owned by the container. In the first code path, there was a memory leak fixed on commit f8d4796a, but it didn't consider the second path, leading to a segmentation fault because the owner of the widget is not us. This patch unrefs early the video area widget in the first path avoiding to unref it twice in the second path. https://bugzilla.gnome.org/show_bug.cgi?id=781904 --- playback/player/gtk/gtk-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 8ae0fea01c..c961198c8a 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1425,6 +1425,7 @@ create_ui (GtkPlay * play) play->video_area = gst_player_gtk_video_renderer_get_widget (GST_PLAYER_GTK_VIDEO_RENDERER (play->renderer)); + g_object_unref (play->video_area); } else { play->renderer = gst_player_video_overlay_video_renderer_new (NULL); @@ -1778,7 +1779,6 @@ gtk_play_dispose (GObject * object) g_object_unref (self->player); } self->player = NULL; - g_clear_object (&self->video_area); G_OBJECT_CLASS (gtk_play_parent_class)->dispose (object); } From d909deaaf4379614e9b0bc2ba8d3c48cf04fadea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Fri, 28 Apr 2017 17:20:07 +0200 Subject: [PATCH 190/412] playback/player: gtk_menu_popup() is deprecated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gtk_menu_popup() is deprecated in GTK+ 3.22. This patch adds a compiler guard to use gtk_menu_popup at pointer() if GTK+ is 3.22 or bigger. https://bugzilla.gnome.org/show_bug.cgi?id=781904 --- playback/player/gtk/gtk-play.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index c961198c8a..63a03bc8c5 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1263,9 +1263,13 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_menu_shell_append (GTK_MENU_SHELL (menu), quit); gtk_widget_show_all (menu); +#if GTK_CHECK_VERSION(3,22,00) + gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *) event); +#else gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0, gdk_event_get_time ((GdkEvent *) event)); +#endif if (media_info) g_object_unref (media_info); From f7ce5cd6945b7e35599921e5d21ea4dc9d9413c3 Mon Sep 17 00:00:00 2001 From: Simon Himmelbauer Date: Mon, 15 May 2017 12:02:03 +0300 Subject: [PATCH 191/412] playback/player/qt: Use QUrl::toEncoded() instead of ::toString() The former properly encodes the URL components to give a valid URL, the latter generates a human-readable URL. --- playback/player/qt/qgstplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/qt/qgstplayer.cpp b/playback/player/qt/qgstplayer.cpp index 5def6fb0fb..6667e8d6c4 100644 --- a/playback/player/qt/qgstplayer.cpp +++ b/playback/player/qt/qgstplayer.cpp @@ -495,7 +495,7 @@ void Player::onEndOfStreamReached(Player *player) void Player::setUri(QUrl url) { Q_ASSERT(player_ != 0); - QByteArray uri = url.toString().toLocal8Bit(); + QByteArray uri = url.toEncoded(); gst_player_set_uri(player_, uri.data()); From 0436ec5b1c75222f51f5e96f1ba1ce9168bbe548 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 20 May 2017 17:25:30 +0200 Subject: [PATCH 192/412] Support GSTREAMER_ROOT_ANDROID env var If GSTREAMER_ROOT_ANDROID is set, try using that for the location of the GStreamer Android tree, but still allow it to be overridden in local gradle properties --- playback/player/android/app/build.gradle | 5 ++++- playback/player/android/gradle.properties | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/playback/player/android/app/build.gradle b/playback/player/android/app/build.gradle index 961c5fb8e5..48b718d6b2 100644 --- a/playback/player/android/app/build.gradle +++ b/playback/player/android/app/build.gradle @@ -19,7 +19,10 @@ android { if (project.hasProperty('gstAndroidRoot')) gstRoot = project.gstAndroidRoot else - throw new GradleException('"gstAndroidRoot" must be defined in your gradle.properties to the top level directory of the unpacked universal GStreamer Android binaries') + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src/main/java", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/main/assets" diff --git a/playback/player/android/gradle.properties b/playback/player/android/gradle.properties index a2a6ee4bd2..8334aac80c 100644 --- a/playback/player/android/gradle.properties +++ b/playback/player/android/gradle.properties @@ -16,4 +16,8 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -gstAndroidRoot=/home/slomo/Projects/gstreamer/head/gst-examples/playback/player/gst-android + +# gstAndroidRoot can be set to point to the unpacked GStreamer android top-level directory +# containing each architecture in subdirectories, or else set the GSTREAMER_ROOT_ANDROID +# environment variable to that location +# gstAndroidRoot=/path/to/gstreamer/android From 1116ae85e28c40ea162f90dab4f66d4d9568aeb4 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 20 May 2017 17:28:41 +0200 Subject: [PATCH 193/412] Play.java: Don't lookup R.id.surface_video twice. R.id.surface_video was stored in the gsv variable just above, don't look it up a second time. --- .../src/main/java/org/freedesktop/gstreamer/player/Play.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java index 2874f05de5..f1cb0cc804 100644 --- a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java +++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java @@ -126,8 +126,7 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC } }); - SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); - SurfaceHolder sh = sv.getHolder(); + SurfaceHolder sh = gsv.getHolder(); sh.addCallback(this); String mediaUri = null; From 1e0b3ed77824f28c9994a9dcc84b0be67397f5b3 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 20 May 2017 17:30:08 +0200 Subject: [PATCH 194/412] Don't crash if no uri is passed in the intent If we receive an intent with no attached data, don't throw a null pointer exception - just don't set a URI on the player. --- .../org/freedesktop/gstreamer/player/Play.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java index f1cb0cc804..ec6e5cfc17 100644 --- a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java +++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java @@ -133,15 +133,17 @@ public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarC Intent intent = getIntent(); android.net.Uri uri = intent.getData(); Log.i ("GStreamer", "Received URI: " + uri); - if (uri.getScheme().equals("content")) { - android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null); - cursor.moveToFirst(); - mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA)); - cursor.close(); - } else { - mediaUri = uri.toString(); + if (uri != null) { + if (uri.getScheme().equals("content")) { + android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null); + cursor.moveToFirst(); + mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA)); + cursor.close(); + } else { + mediaUri = uri.toString(); + } + player.setUri(mediaUri); } - player.setUri(mediaUri); updateTimeWidget(); } From 9ebe002f5148588c982731e10d06621519c23d95 Mon Sep 17 00:00:00 2001 From: Simon Himmelbauer Date: Tue, 23 May 2017 16:00:14 +0200 Subject: [PATCH 195/412] playback/player/ios: Updated the iOS example in order to be compatible with the latest GStreamer as well as Apple Hardware -) Disabled bitcode generation -) Removed/Renamed several plugins in the gst_ios_init.m --- .../ios/GstPlay.xcodeproj/project.pbxproj | 31 +++++++++++++---- .../player/ios/GstPlay/GstPlay-Info.plist | 4 ++- playback/player/ios/GstPlay/gst_ios_init.m | 34 +++++-------------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj index 6d9ff1f322..d47deb1d7e 100644 --- a/playback/player/ios/GstPlay.xcodeproj/project.pbxproj +++ b/playback/player/ios/GstPlay.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ AD2B885C198D65470070367B /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD2B885A198D65470070367B /* MainStoryboard_iPad.storyboard */; }; AD2B8861198D65780070367B /* LibraryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B885E198D65780070367B /* LibraryViewController.m */; }; AD2B8862198D65780070367B /* VideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2B8860198D65780070367B /* VideoViewController.m */; }; + F4D8C7D71ED455260019B8A3 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D8C7D61ED455260019B8A3 /* libiconv.tbd */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -51,6 +52,7 @@ AD2B885E198D65780070367B /* LibraryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LibraryViewController.m; sourceTree = ""; }; AD2B885F198D65780070367B /* VideoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoViewController.h; sourceTree = ""; }; AD2B8860198D65780070367B /* VideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoViewController.m; sourceTree = ""; }; + F4D8C7D61ED455260019B8A3 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -58,6 +60,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F4D8C7D71ED455260019B8A3 /* libiconv.tbd in Frameworks */, AD2B8821198D631B0070367B /* CoreGraphics.framework in Frameworks */, AD2B8825198D631B0070367B /* GStreamer.framework in Frameworks */, AD2B8823198D631B0070367B /* UIKit.framework in Frameworks */, @@ -88,6 +91,7 @@ AD2B881D198D631B0070367B /* Frameworks */ = { isa = PBXGroup; children = ( + F4D8C7D61ED455260019B8A3 /* libiconv.tbd */, AD2B881E198D631B0070367B /* Foundation.framework */, AD2B8820198D631B0070367B /* CoreGraphics.framework */, AD2B8822198D631B0070367B /* UIKit.framework */, @@ -157,7 +161,7 @@ AD2B8813198D631B0070367B /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = "Sebastian Dröge"; }; buildConfigurationList = AD2B8816198D631B0070367B /* Build configuration list for PBXProject "GstPlay" */; @@ -234,13 +238,20 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -253,7 +264,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 6.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -273,20 +284,26 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 6.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -296,7 +313,6 @@ AD2B8851198D631B0070367B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = armv7; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; FRAMEWORK_SEARCH_PATHS = ( @@ -310,7 +326,7 @@ ../lib, ); INFOPLIST_FILE = "GstPlay/GstPlay-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 6.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; OTHER_LDFLAGS = ( "-lresolv", "-lstdc++", @@ -339,6 +355,7 @@ "-framework", AssetsLibrary, ); + PRODUCT_BUNDLE_IDENTIFIER = simon.himmelbauer; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -347,7 +364,6 @@ AD2B8852198D631B0070367B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = armv7; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; FRAMEWORK_SEARCH_PATHS = ( @@ -361,7 +377,7 @@ ../lib, ); INFOPLIST_FILE = "GstPlay/GstPlay-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 6.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; OTHER_LDFLAGS = ( "-lresolv", "-lstdc++", @@ -390,6 +406,7 @@ "-framework", AssetsLibrary, ); + PRODUCT_BUNDLE_IDENTIFIER = simon.himmelbauer; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; diff --git a/playback/player/ios/GstPlay/GstPlay-Info.plist b/playback/player/ios/GstPlay/GstPlay-Info.plist index 180da25351..107fdcdffc 100644 --- a/playback/player/ios/GstPlay/GstPlay-Info.plist +++ b/playback/player/ios/GstPlay/GstPlay-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - org.freedesktop.gstreamer.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -24,6 +24,8 @@ 1.0 LSRequiresIPhoneOS + NSPhotoLibraryUsageDescription + This app requires access to the photo library for demo purposes. UIMainStoryboardFile MainStoryboard_iPhone UIMainStoryboardFile~ipad diff --git a/playback/player/ios/GstPlay/gst_ios_init.m b/playback/player/ios/GstPlay/gst_ios_init.m index 214b2e8bc3..aea37caf99 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.m +++ b/playback/player/ios/GstPlay/gst_ios_init.m @@ -99,9 +99,6 @@ GST_PLUGIN_STATIC_DECLARE(dvdsub); #if defined(GST_IOS_PLUGIN_DVDLPCMDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_DECLARE(dvdlpcmdec); #endif -#if defined(GST_IOS_PLUGIN_MAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) -GST_PLUGIN_STATIC_DECLARE(mad); -#endif #if defined(GST_IOS_PLUGIN_MPEG2DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_DECLARE(mpeg2dec); #endif @@ -435,11 +432,8 @@ GST_PLUGIN_STATIC_DECLARE(pnm); #if defined(GST_IOS_PLUGIN_RFBSRC) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(rfbsrc); #endif -#if defined(GST_IOS_PLUGIN_SCHRO) || defined(GST_IOS_PLUGINS_CODECS) -GST_PLUGIN_STATIC_DECLARE(schro); -#endif -#if defined(GST_IOS_PLUGIN_GSTSIREN) || defined(GST_IOS_PLUGINS_CODECS) -GST_PLUGIN_STATIC_DECLARE(gstsiren); +#if defined(GST_IOS_PLUGIN_SIREN) || defined(GST_IOS_PLUGINS_CODECS) +GST_PLUGIN_STATIC_DECLARE(siren); #endif #if defined(GST_IOS_PLUGIN_SMOOTHSTREAMING) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_DECLARE(smoothstreaming); @@ -486,11 +480,8 @@ GST_PLUGIN_STATIC_DECLARE(soup); #if defined(GST_IOS_PLUGIN_UDP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_DECLARE(udp); #endif -#if defined(GST_IOS_PLUGIN_DATAURISRC) || defined(GST_IOS_PLUGINS_NET) -GST_PLUGIN_STATIC_DECLARE(dataurisrc); -#endif -#if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) -GST_PLUGIN_STATIC_DECLARE(sdp); +#if defined(GST_IOS_PLUGIN_SDPELEM) || defined(GST_IOS_PLUGINS_NET) +GST_PLUGIN_STATIC_DECLARE(sdpelem); #endif #if defined(GST_IOS_PLUGIN_SRTP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_DECLARE(srtp); @@ -638,9 +629,6 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_DVDLPCMDEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_REGISTER(dvdlpcmdec); #endif -#if defined(GST_IOS_PLUGIN_MAD) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) - GST_PLUGIN_STATIC_REGISTER(mad); -#endif #if defined(GST_IOS_PLUGIN_MPEG2DEC) || defined(GST_IOS_PLUGINS_CODECS_RESTRICTED) GST_PLUGIN_STATIC_REGISTER(mpeg2dec); #endif @@ -974,11 +962,8 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_RFBSRC) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(rfbsrc); #endif -#if defined(GST_IOS_PLUGIN_SCHRO) || defined(GST_IOS_PLUGINS_CODECS) - GST_PLUGIN_STATIC_REGISTER(schro); -#endif -#if defined(GST_IOS_PLUGIN_GSTSIREN) || defined(GST_IOS_PLUGINS_CODECS) - GST_PLUGIN_STATIC_REGISTER(gstsiren); +#if defined(GST_IOS_PLUGIN_SIREN) || defined(GST_IOS_PLUGINS_CODECS) + GST_PLUGIN_STATIC_REGISTER(siren); #endif #if defined(GST_IOS_PLUGIN_SMOOTHSTREAMING) || defined(GST_IOS_PLUGINS_CODECS) GST_PLUGIN_STATIC_REGISTER(smoothstreaming); @@ -1025,11 +1010,8 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_UDP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_REGISTER(udp); #endif -#if defined(GST_IOS_PLUGIN_DATAURISRC) || defined(GST_IOS_PLUGINS_NET) - GST_PLUGIN_STATIC_REGISTER(dataurisrc); -#endif -#if defined(GST_IOS_PLUGIN_SDP) || defined(GST_IOS_PLUGINS_NET) - GST_PLUGIN_STATIC_REGISTER(sdp); +#if defined(GST_IOS_PLUGIN_SDPELEM) || defined(GST_IOS_PLUGINS_NET) + GST_PLUGIN_STATIC_REGISTER(sdpelem); #endif #if defined(GST_IOS_PLUGIN_SRTP) || defined(GST_IOS_PLUGINS_NET) GST_PLUGIN_STATIC_REGISTER(srtp); From d23171fbdff30eb33e1174e94ec0aff2cbb7578f Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Wed, 26 Jul 2017 17:27:11 +0100 Subject: [PATCH 196/412] gtk-play: fix deprecation warnings --- playback/player/gtk/gtk-play.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 63a03bc8c5..6a8670eef8 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1103,11 +1103,11 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) current_index = get_current_track_index (play, type); if (type == GST_TYPE_PLAYER_VIDEO_INFO) - list = gst_player_get_video_streams (media_info); + list = gst_player_media_info_get_video_streams (media_info); else if (type == GST_TYPE_PLAYER_AUDIO_INFO) - list = gst_player_get_audio_streams (media_info); + list = gst_player_media_info_get_audio_streams (media_info); else - list = gst_player_get_subtitle_streams (media_info); + list = gst_player_media_info_get_subtitle_streams (media_info); menu = gtk_menu_new (); @@ -1192,7 +1192,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) media_info = gst_player_get_media_info (play->player); - if (media_info && !gst_player_get_video_streams (media_info)) + if (media_info && !gst_player_media_info_get_video_streams (media_info)) gtk_widget_set_sensitive (video, FALSE); else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_VIDEO_INFO); @@ -1202,7 +1202,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_widget_set_sensitive (video, FALSE); } - if (media_info && !gst_player_get_audio_streams (media_info)) + if (media_info && !gst_player_media_info_get_audio_streams (media_info)) gtk_widget_set_sensitive (audio, FALSE); else { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_AUDIO_INFO); @@ -1214,15 +1214,15 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) /* enable visualization menu for audio stream */ if (media_info && - gst_player_get_audio_streams (media_info) && - !gst_player_get_video_streams (media_info)) { + gst_player_media_info_get_audio_streams (media_info) && + !gst_player_media_info_get_video_streams (media_info)) { submenu = create_visualization_menu (play); gtk_menu_item_set_submenu (GTK_MENU_ITEM (vis), submenu); } else { gtk_widget_set_sensitive (vis, FALSE); } - if (media_info && gst_player_get_video_streams (media_info)) { + if (media_info && gst_player_media_info_get_video_streams (media_info)) { submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_SUBTITLE_INFO); gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); From e88c04ff34cf56984e62a524a0bcc16ab51b282d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 8 Aug 2017 12:02:39 +0300 Subject: [PATCH 197/412] gst-play: Fix more deprecation warnings --- playback/player/gst-play/gst-play.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index bd21bd27af..d4c9e67ed2 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -240,7 +240,7 @@ print_all_video_stream (GstPlayerMediaInfo * media_info) { GList *list, *l; - list = gst_player_get_video_streams (media_info); + list = gst_player_media_info_get_video_streams (media_info); if (!list) return; @@ -259,7 +259,7 @@ print_all_subtitle_stream (GstPlayerMediaInfo * media_info) { GList *list, *l; - list = gst_player_get_subtitle_streams (media_info); + list = gst_player_media_info_get_subtitle_streams (media_info); if (!list) return; @@ -278,7 +278,7 @@ print_all_audio_stream (GstPlayerMediaInfo * media_info) { GList *list, *l; - list = gst_player_get_audio_streams (media_info); + list = gst_player_media_info_get_audio_streams (media_info); if (!list) return; From 9dfe9fb41dd1475175be3e40ccbd4379f479e82c Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Thu, 17 Aug 2017 19:31:51 +1000 Subject: [PATCH 198/412] player/ios: constrain the bottom of the video view to the top of the toolbar Otherwise, no video output on iPad. --- playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard | 1 + 1 file changed, 1 insertion(+) diff --git a/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard b/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard index d1ea519820..4fd3b54152 100644 --- a/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard +++ b/playback/player/ios/GstPlay/MainStoryboard_iPad.storyboard @@ -99,6 +99,7 @@ + From fed396ff8e50a279aa0f293d74c3d11c7559a9e8 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Thu, 12 Oct 2017 16:26:41 +1100 Subject: [PATCH 199/412] playback/android: expose after setting the window handle Fixes the view not updating in PAUSED after a surface change from an orientation change/explicit surface change. --- playback/player/android/app/src/main/jni/player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playback/player/android/app/src/main/jni/player.c b/playback/player/android/app/src/main/jni/player.c index 58d177ebc1..769c75c96e 100644 --- a/playback/player/android/app/src/main/jni/player.c +++ b/playback/player/android/app/src/main/jni/player.c @@ -407,6 +407,8 @@ native_set_surface (JNIEnv * env, jobject thiz, jobject surface) gst_player_video_overlay_video_renderer_set_window_handle (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer), (gpointer) new_native_window); + gst_player_video_overlay_video_renderer_expose + (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer)); } static void From 8d782e44602adf42f0f221a315aad3a1ff885612 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 21 Oct 2017 19:43:01 +0530 Subject: [PATCH 200/412] Initial commit --- webrtc/.gitignore | 52 +++++++++++++++++++++++++++++++++++++++++++++++ webrtc/LICENSE | 25 +++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 webrtc/.gitignore create mode 100644 webrtc/LICENSE diff --git a/webrtc/.gitignore b/webrtc/.gitignore new file mode 100644 index 0000000000..c6127b38c1 --- /dev/null +++ b/webrtc/.gitignore @@ -0,0 +1,52 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/webrtc/LICENSE b/webrtc/LICENSE new file mode 100644 index 0000000000..bd5093440e --- /dev/null +++ b/webrtc/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2017, Centricular +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 663ad7ba98237801f2c6d4b7080f45ccf33af171 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 21 Oct 2017 19:56:52 +0530 Subject: [PATCH 201/412] Add a simple python3 webrtc signalling server + client for testing + protocol documentation --- webrtc/signalling/Protocol.md | 80 ++++++++++++ webrtc/signalling/README.md | 26 ++++ webrtc/signalling/client.py | 85 +++++++++++++ webrtc/signalling/generate_cert.sh | 3 + webrtc/signalling/simple-server.py | 187 +++++++++++++++++++++++++++++ 5 files changed, 381 insertions(+) create mode 100644 webrtc/signalling/Protocol.md create mode 100644 webrtc/signalling/README.md create mode 100755 webrtc/signalling/client.py create mode 100755 webrtc/signalling/generate_cert.sh create mode 100755 webrtc/signalling/simple-server.py diff --git a/webrtc/signalling/Protocol.md b/webrtc/signalling/Protocol.md new file mode 100644 index 0000000000..1af892eee3 --- /dev/null +++ b/webrtc/signalling/Protocol.md @@ -0,0 +1,80 @@ +# Terminology + +### Client + +A GStreamer-based application + +### Browser + +A JS application that runs in the browser and uses built-in browser webrtc APIs + +### Peer + +Any webrtc-using application that can participate in a call + +### Signalling server + +Basic websockets server implemented in Python that manages the peers list and shovels data between peers + +# Overview + +This is a basic protocol for doing 1-1 audio+video calls between a gstreamer app and a JS app in a browser. + +# Peer registration and calling + +Peers must register with the signalling server before a call can be initiated. The server connection should stay open as long as the peer is available or in a call. + +This protocol builds upon https://github.com/shanet/WebRTC-Example/ + +* Connect to the websocket server +* Send `HELLO ` where `` is a string which will uniquely identify this peer +* Receive `HELLO` +* Any other message starting with `ERROR` is an error. + +* To connect to a peer, send `SESSION ` where `` identifies the peer to connect to, and receive `SESSION_OK` +* All further messages will be forwarded to the peer +* The call negotiation with the peer can be started by sending JSON encoded SDP and ICE + +* Closure of the server connection means the call has ended; either because the other peer ended it or went away +* To end the call, disconnect from the server. You may reconnect again whenever you wish. + +# Negotiation + +Once a call has been setup with the signalling server, the peers must negotiate SDP and ICE candidates with each other. + +The calling side must create an SDP offer and send it to the peer as a JSON object: + +```json +{ + "sdp": { + "sdp": "o=- [....]", + "type": "offer" + } +} +``` + +The callee must then reply with an answer: + +```json +{ + "sdp": { + "sdp": "o=- [....]", + "type": "answer" + } +} +``` + +ICE candidates must be exchanged similarly by exchanging JSON objects: + + +```json +{ + "ice": { + "candidate": ..., + "sdpMLineIndex": ..., + ... + } +} +``` + +Note that the structure of these is the same as that specified by the WebRTC spec. diff --git a/webrtc/signalling/README.md b/webrtc/signalling/README.md new file mode 100644 index 0000000000..898178208a --- /dev/null +++ b/webrtc/signalling/README.md @@ -0,0 +1,26 @@ +## Overview + +Read Protocol.md + +## Dependencies + +* Python 3 +* pip3 install --user websockets + +## Example usage + +In three separate tabs, run consecutively: + +```console +$ ./generate_certs.sh +$ ./simple-server.py +``` + +```console +$ ./client.py +Our uid is 'ws-test-client-8f63b9' +``` + +```console +$ ./client.py --call ws-test-client-8f63b9 +``` diff --git a/webrtc/signalling/client.py b/webrtc/signalling/client.py new file mode 100755 index 0000000000..0fa8227d78 --- /dev/null +++ b/webrtc/signalling/client.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# Test client for simple 1-1 call signalling server +# +# Copyright (C) 2017 Centricular Ltd. +# +# Author: Nirbheek Chauhan +# + +import sys +import ssl +import json +import uuid +import asyncio +import websockets +import argparse + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--url', default='wss://localhost:8443', help='URL to connect to') +parser.add_argument('--call', default=None, help='uid of peer to call') + +options = parser.parse_args(sys.argv[1:]) + +SERVER_ADDR = options.url +CALLEE_ID = options.call +PEER_ID = 'ws-test-client-' + str(uuid.uuid4())[:6] + +sslctx = False +if SERVER_ADDR.startswith(('wss://', 'https://')): + sslctx = ssl.create_default_context() + # FIXME + sslctx.check_hostname = False + sslctx.verify_mode = ssl.CERT_NONE + +def reply_sdp_ice(msg): + # Here we'd parse the incoming JSON message for ICE and SDP candidates + print("Got: " + msg) + reply = json.dumps({'sdp': 'reply sdp'}) + print("Sent: " + reply) + return reply + +def send_sdp_ice(): + reply = json.dumps({'sdp': 'initial sdp'}) + print("Sent: " + reply) + return reply + +async def hello(): + async with websockets.connect(SERVER_ADDR, ssl=sslctx) as ws: + await ws.send('HELLO ' + PEER_ID) + assert(await ws.recv() == 'HELLO') + + # Initiate call if requested + if CALLEE_ID: + await ws.send('CALL {}'.format(CALLEE_ID)) + + # Receive messages + while True: + msg = await ws.recv() + if msg.startswith('ERROR'): + # On error, we bring down the webrtc pipeline, etc + print('{!r}, exiting'.format(msg)) + return + if CALLEE_ID: + if msg == 'CALL_OK': + await ws.send(send_sdp_ice()) + # Return so we don't have an infinite loop + return + else: + print('Unknown reply: {!r}, exiting'.format(msg)) + return + else: + await ws.send(reply_sdp_ice(msg)) + # Return so we don't have an infinite loop + return + +print('Our uid is {!r}'.format(PEER_ID)) + +try: + asyncio.get_event_loop().run_until_complete(hello()) +except websockets.exceptions.InvalidHandshake: + print('Invalid handshake: are you sure this is a websockets server?\n') + raise +except ssl.SSLError: + print('SSL Error: are you sure the server is using TLS?\n') + raise diff --git a/webrtc/signalling/generate_cert.sh b/webrtc/signalling/generate_cert.sh new file mode 100755 index 0000000000..68a4b96f3c --- /dev/null +++ b/webrtc/signalling/generate_cert.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes diff --git a/webrtc/signalling/simple-server.py b/webrtc/signalling/simple-server.py new file mode 100755 index 0000000000..34af863f06 --- /dev/null +++ b/webrtc/signalling/simple-server.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# +# Example 1-1 call signalling server +# +# Copyright (C) 2017 Centricular Ltd. +# +# Author: Nirbheek Chauhan +# + +import os +import sys +import ssl +import logging +import asyncio +import websockets +import argparse + +from concurrent.futures._base import TimeoutError + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--addr', default='0.0.0.0', help='Address to listen on') +parser.add_argument('--port', default=8443, type=int, help='Port to listen on') +parser.add_argument('--keepalive-timeout', dest='keepalive_timeout', default=30, type=int, help='Timeout for keepalive (in seconds)') +parser.add_argument('--cert-path', default=os.path.dirname(__file__)) + +options = parser.parse_args(sys.argv[1:]) + +ADDR_PORT = (options.addr, options.port) +KEEPALIVE_TIMEOUT = options.keepalive_timeout + +# Format: {uid: (Peer WebSocketServerProtocol, remote_address)} +peers = dict() +# Format: {caller_uid: callee_uid, +# callee_uid: caller_uid} +# Bidirectional mapping between the two peers +sessions = dict() + +############### Helper functions ############### + +async def recv_msg_ping(ws, raddr): + ''' + Wait for a message forever, and send a regular ping to prevent bad routers + from closing the connection. + ''' + msg = None + while msg is None: + try: + msg = await asyncio.wait_for(ws.recv(), KEEPALIVE_TIMEOUT) + except TimeoutError: + print('Sending keepalive ping to {!r} in recv'.format(raddr)) + await ws.ping() + return msg + +async def disconnect(ws, peer_id): + ''' + Remove @peer_id from the list of sessions and close our connection to it. + This informs the peer that the session and all calls have ended, and it + must reconnect. + ''' + global sessions + if peer_id in sessions: + del sessions[peer_id] + # Close connection + if ws and ws.open: + # Don't care about errors + asyncio.ensure_future(ws.close(reason='hangup')) + +async def remove_peer(uid): + if uid in sessions: + other_id = sessions[uid] + del sessions[uid] + print("Cleaned up {} session".format(uid)) + if other_id in sessions: + del sessions[other_id] + print("Also cleaned up {} session".format(other_id)) + # If there was a session with this peer, also + # close the connection to reset its state. + if other_id in peers: + print("Closing connection to {}".format(other_id)) + wso, oaddr = peers[other_id] + del peers[other_id] + await wso.close() + if uid in peers: + ws, raddr = peers[uid] + del peers[uid] + await ws.close() + print("Disconnected from peer {!r} at {!r}".format(uid, raddr)) + +############### Handler functions ############### + +async def connection_handler(ws, peer_id): + global peers, sessions + raddr = ws.remote_address + peers[peer_id] = (ws, raddr) + print("Registered peer {!r} at {!r}".format(peer_id, raddr)) + while True: + # Receive command, wait forever if necessary + msg = await recv_msg_ping(ws, raddr) + if msg.startswith('SESSION'): + print("{!r} command {!r}".format(peer_id, msg)) + _, callee_id = msg.split(maxsplit=1) + if callee_id not in peers: + await ws.send('ERROR peer {!r} not found'.format(callee_id)) + continue + if callee_id in sessions: + await ws.send('ERROR peer {!r} busy'.format(callee_id)) + continue + await ws.send('SESSION_OK') + wsc = peers[callee_id][0] + print("Session from {!r} ({!r}) to {!r} ({!r})".format(peer_id, raddr, callee_id, + wsc.remote_address)) + # Register call + sessions[peer_id] = callee_id + sessions[callee_id] = peer_id + # We're in a session, route message to connected peer + elif peer_id in sessions: + other_id = sessions[peer_id] + wso, oaddr = peers[other_id] + print("{} -> {}: {}".format(peer_id, other_id, msg)) + await wso.send(msg) + +async def hello_peer(ws): + ''' + Exchange hello, register peer + ''' + raddr = ws.remote_address + hello = await ws.recv() + hello, uid = hello.split(maxsplit=1) + if hello != 'HELLO': + await ws.close(code=1002, reason='invalid protocol') + raise Exception("Invalid hello from {!r}".format(raddr)) + if not uid or uid in peers: + await ws.close(code=1002, reason='invalid peer uid') + raise Exception("Invalid uid {!r} from {!r}".format(uid, raddr)) + # Send back a HELLO + await ws.send('HELLO') + return uid + +async def handler(ws, path): + ''' + All incoming messages are handled here. @path is unused. + ''' + raddr = ws.remote_address + print("Connected to {!r}".format(raddr)) + peer_id = await hello_peer(ws) + try: + await connection_handler(ws, peer_id) + except websockets.ConnectionClosed: + print("Connection to peer {!r} closed, exiting handler".format(raddr)) + finally: + await remove_peer(peer_id) + +# Create an SSL context to be used by the websocket server +certpath = options.cert_path +print('Using TLS with keys in {!r}'.format(certpath)) +if 'letsencrypt' in certpath: + chain_pem = os.path.join(certpath, 'fullchain.pem') + key_pem = os.path.join(certpath, 'privkey.pem') +else: + chain_pem = os.path.join(certpath, 'cert.pem') + key_pem = os.path.join(certpath, 'key.pem') + +sslctx = ssl.create_default_context() +try: + sslctx.load_cert_chain(chain_pem, keyfile=key_pem) +except FileNotFoundError: + print("Certificates not found, did you run generate_cert.sh?") + sys.exit(1) +# FIXME +sslctx.check_hostname = False +sslctx.verify_mode = ssl.CERT_NONE + +print("Listening on https://{}:{}".format(*ADDR_PORT)) +# Websocket server +wsd = websockets.serve(handler, *ADDR_PORT, ssl=sslctx, + # Maximum number of messages that websockets will pop + # off the asyncio and OS buffers per connection. See: + # https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol + max_queue=16) + +logger = logging.getLogger('websockets.server') + +logger.setLevel(logging.ERROR) +logger.addHandler(logging.StreamHandler()) + +asyncio.get_event_loop().run_until_complete(wsd) +asyncio.get_event_loop().run_forever() From e9b0656bad4ddc053d5349dc3a419f5bd626855f Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 21 Oct 2017 19:57:29 +0530 Subject: [PATCH 202/412] Add sendrecv implementation in js and gst webrtc JS code runs on the browser and uses the browser's webrtc implementation. C code uses gstreamer's webrtc implementation, for which you need the following repositories: https://github.com/ystreet/gstreamer/tree/promise https://github.com/ystreet/gst-plugins-bad/tree/webrtc You can build these with either Autotools gst-uninstalled: https://arunraghavan.net/2014/07/quick-start-guide-to-gst-uninstalled-1-x/ Or with Meson gst-build: https://cgit.freedesktop.org/gstreamer/gst-build/ --- webrtc/.gitignore | 11 +- webrtc/README.md | 31 ++ webrtc/sendrecv/gst/webrtc-sendrecv.c | 596 ++++++++++++++++++++++++++ webrtc/sendrecv/js/index.html | 26 ++ webrtc/sendrecv/js/webrtc.js | 203 +++++++++ 5 files changed, 859 insertions(+), 8 deletions(-) create mode 100644 webrtc/README.md create mode 100644 webrtc/sendrecv/gst/webrtc-sendrecv.c create mode 100644 webrtc/sendrecv/js/index.html create mode 100644 webrtc/sendrecv/js/webrtc.js diff --git a/webrtc/.gitignore b/webrtc/.gitignore index c6127b38c1..bab2990ef5 100644 --- a/webrtc/.gitignore +++ b/webrtc/.gitignore @@ -42,11 +42,6 @@ *.idb *.pdb -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf +# Our stuff +*.pem +webrtc-sendrecv diff --git a/webrtc/README.md b/webrtc/README.md new file mode 100644 index 0000000000..3c3ae09ef3 --- /dev/null +++ b/webrtc/README.md @@ -0,0 +1,31 @@ +## GStreamer WebRTC demos + +All demos use the same signalling server in the `signalling/` directory + +You will need the following repositories till the GStreamer WebRTC implementation is merged upstream: + +https://github.com/ystreet/gstreamer/tree/promise + +https://github.com/ystreet/gst-plugins-bad/tree/webrtc + +You can build these with either Autotools gst-uninstalled: + +https://arunraghavan.net/2014/07/quick-start-guide-to-gst-uninstalled-1-x/ + +Or with Meson gst-build: + +https://cgit.freedesktop.org/gstreamer/gst-build/ + +### sendrecv: Send and receive audio and video + +* Serve the `js/` directory on the root of your website, or open https://webrtc.nirbheek.in + - The JS code assumes the signalling server is on port 8443 of the same server serving the HTML +* Build and run the sources in the `gst/` directory on your machine + +```console +$ gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o webrtc-sendrecv +``` + +* Open the website in a browser and ensure that the status is "Registered with server, waiting for call", and note the `id` too. +* Run `webrtc-sendrecv --peer-id=ID` with the `id` from the browser. You will see state changes and an SDP exchange. +* You will see a bouncing ball + hear red noise in the browser, and your browser's webcam + mic in the gst app diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c new file mode 100644 index 0000000000..8c25f97cfb --- /dev/null +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -0,0 +1,596 @@ +/* + * Demo gstreamer app for negotiating and streaming a sendrecv webrtc stream + * with a browser JS app. + * + * gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o webrtc-sendrecv + * + * Author: Nirbheek Chauhan + */ +#include +#include +#include + +/* For signalling */ +#include +#include + +#include + +enum AppState { + APP_STATE_UNKNOWN = 0, + APP_STATE_ERROR = 1, /* generic error */ + SERVER_CONNECTING = 1000, + SERVER_CONNECTION_ERROR, + SERVER_CONNECTED, /* Ready to register */ + SERVER_REGISTERING = 2000, + SERVER_REGISTRATION_ERROR, + SERVER_REGISTERED, /* Ready to call a peer */ + SERVER_CLOSED, /* server connection closed by us or the server */ + PEER_CONNECTING = 3000, + PEER_CONNECTION_ERROR, + PEER_CONNECTED, + PEER_CALL_NEGOTIATING = 4000, + PEER_CALL_STARTED, + PEER_CALL_STOPPING, + PEER_CALL_STOPPED, + PEER_CALL_ERROR, +}; + +static GMainLoop *loop; +static GstElement *pipe1, *webrtc1; + +static SoupWebsocketConnection *ws_conn = NULL; +static enum AppState app_state = 0; +static const gchar *peer_id = NULL; +static const gchar *server_url = "wss://webrtc.nirbheek.in:8443"; + +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" }, +}; + +static gboolean +cleanup_and_quit_loop (const gchar * msg, enum AppState state) +{ + if (msg) + g_printerr ("%s\n", msg); + if (state > 0) + app_state = state; + + if (ws_conn) { + if (soup_websocket_connection_get_state (ws_conn) == + SOUP_WEBSOCKET_STATE_OPEN) + /* This will call us again */ + soup_websocket_connection_close (ws_conn, 1000, ""); + else + g_object_unref (ws_conn); + } + + if (loop) { + g_main_loop_quit (loop); + loop = NULL; + } + + /* To allow usage as a GSourceFunc */ + return G_SOURCE_REMOVE; +} + +static gchar* +get_string_from_json_object (JsonObject * object) +{ + JsonNode *root; + JsonGenerator *generator; + gchar *text; + + /* Make it the root node */ + root = json_node_init_object (json_node_alloc (), object); + generator = json_generator_new (); + json_generator_set_root (generator, root); + text = json_generator_to_data (generator, NULL); + + /* Release everything */ + g_object_unref (generator); + json_node_free (root); + return text; +} + +static void +handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, + const char * sink_name) +{ + GstPad *qpad; + GstElement *q, *conv, *sink; + GstPadLinkReturn ret; + + q = gst_element_factory_make ("queue", NULL); + g_assert (q); + conv = gst_element_factory_make (convert_name, NULL); + g_assert (conv); + sink = gst_element_factory_make (sink_name, NULL); + g_assert (sink); + gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, sink, NULL); + + qpad = gst_element_get_static_pad (q, "sink"); + + ret = gst_pad_link (pad, qpad); + g_assert (ret == GST_PAD_LINK_OK); +} + +static void +on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, + GstElement * pipe) +{ + GstCaps *caps; + const gchar *name; + + if (!gst_pad_has_current_caps (pad)) { + g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + GST_PAD_NAME (pad)); + return; + } + + caps = gst_pad_get_current_caps (pad); + name = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + + if (g_str_has_prefix (name, "video")) { + handle_media_stream (pad, pipe, "videoconvert", "autovideosink"); + } else if (g_str_has_prefix (name, "audio")) { + handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); + } else { + g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + } +} + +static void +on_incoming_stream (GstElement * webrtc, GstPad * pad, GstElement * pipe) +{ + GstElement *decodebin; + + if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) + return; + + decodebin = gst_element_factory_make ("decodebin", NULL); + g_signal_connect (decodebin, "pad-added", + G_CALLBACK (on_incoming_decodebin_stream), pipe); + gst_bin_add (GST_BIN (pipe), decodebin); + gst_element_sync_state_with_parent (decodebin); + gst_element_link (webrtc, decodebin); +} + +static void +send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex, + gchar * candidate, gpointer user_data G_GNUC_UNUSED) +{ + gchar *text; + JsonObject *ice, *msg; + + if (app_state < PEER_CALL_NEGOTIATING) { + cleanup_and_quit_loop ("Can't send ICE, not in call", APP_STATE_ERROR); + return; + } + + 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); + + soup_websocket_connection_send_text (ws_conn, text); + g_free (text); +} + +static void +send_sdp_offer (GstWebRTCSessionDescription * offer) +{ + 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); + return; + } + + text = gst_sdp_message_as_text (offer->sdp); + g_print ("Sending offer:\n%s\n", text); + + sdp = json_object_new (); + json_object_set_string_member (sdp, "type", "offer"); + json_object_set_string_member (sdp, "sdp", text); + g_free (text); + + msg = json_object_new (); + json_object_set_object_member (msg, "sdp", sdp); + text = get_string_from_json_object (msg); + json_object_unref (msg); + + soup_websocket_connection_send_text (ws_conn, text); + g_free (text); +} + +/* Offer created by our pipeline, to be sent to the peer */ +static void +on_offer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *offer = NULL; + gchar *desc; + + g_assert (app_state == PEER_CALL_NEGOTIATING); + + g_assert (promise->result == GST_PROMISE_RESULT_REPLIED); + gst_structure_get (promise->promise, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + gst_promise_unref (promise); + + promise = gst_promise_new (); + g_signal_emit_by_name (webrtc1, "set-local-description", offer, promise); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + + /* Send offer to peer */ + send_sdp_offer (offer); + gst_webrtc_session_description_free (offer); +} + +static void +on_negotiation_needed (GstElement * element, gpointer user_data) +{ + GstPromise *promise = gst_promise_new (); + + app_state = PEER_CALL_NEGOTIATING; + g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); + gst_promise_set_change_callback (promise, on_offer_received, user_data, + NULL); +} + +#define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=" +#define RTP_CAPS_VP8 "application/x-rtp,media=video,encoding-name=VP8,payload=" + +static gboolean +start_pipeline (void) +{ + GstStateChangeReturn ret; + GError *error = NULL; + + pipe1 = + gst_parse_launch ("webrtcbin name=sendrecv " + "videotestsrc pattern=ball ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " + "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " + "audiotestsrc wave=red-noise ! queue ! opusenc ! rtpopuspay ! " + "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", + &error); + + if (error) { + g_printerr ("Failed to parse launch: %s\n", error->message); + g_error_free (error); + goto err; + } + + webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "sendrecv"); + g_assert (webrtc1 != NULL); + + /* This is the gstwebrtc entry point where we create the offer and so on. It + * will be called when the pipeline goes to PLAYING. */ + g_signal_connect (webrtc1, "on-negotiation-needed", + G_CALLBACK (on_negotiation_needed), NULL); + /* We need to transmit this ICE candidate to the browser via the websockets + * signalling server. Incoming ice candidates from the browser need to be + * added by us too, see on_server_message() */ + g_signal_connect (webrtc1, "on-ice-candidate", + G_CALLBACK (send_ice_candidate_message), NULL); + /* Incoming streams will be exposed via this signal */ + g_signal_connect (webrtc1, "pad-added", G_CALLBACK (on_incoming_stream), + pipe1); + /* Lifetime is the same as the pipeline itself */ + gst_object_unref (webrtc1); + + g_print ("Starting pipeline\n"); + ret = gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + goto err; + + return TRUE; + +err: + if (pipe1) + g_clear_object (&pipe1); + if (webrtc1) + webrtc1 = NULL; + return FALSE; +} + +static gboolean +setup_call (void) +{ + gchar *msg; + + if (soup_websocket_connection_get_state (ws_conn) != + SOUP_WEBSOCKET_STATE_OPEN) + return FALSE; + + if (!peer_id) + return FALSE; + + g_print ("Setting up signalling server call with %s\n", peer_id); + app_state = PEER_CONNECTING; + msg = g_strdup_printf ("SESSION %s", peer_id); + soup_websocket_connection_send_text (ws_conn, msg); + g_free (msg); + return TRUE; +} + +static gboolean +register_with_server (void) +{ + gchar *hello; + gint32 our_id; + + if (soup_websocket_connection_get_state (ws_conn) != + SOUP_WEBSOCKET_STATE_OPEN) + return FALSE; + + our_id = g_random_int_range (10, 10000); + g_print ("Registering id %i with server\n", our_id); + app_state = SERVER_REGISTERING; + + /* Register with the server with a random integer id. Reply will be received + * by on_server_message() */ + hello = g_strdup_printf ("HELLO %i", our_id); + soup_websocket_connection_send_text (ws_conn, hello); + g_free (hello); + + return TRUE; +} + +static void +on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + app_state = SERVER_CLOSED; + cleanup_and_quit_loop ("Server connection closed", 0); +} + +/* One mega message handler for our asynchronous calling mechanism */ +static void +on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, + GBytes * message, gpointer user_data) +{ + gsize size; + gchar *text, *data; + + switch (type) { + case SOUP_WEBSOCKET_DATA_BINARY: + g_printerr ("Received unknown binary message, ignoring\n"); + g_bytes_unref (message); + return; + case SOUP_WEBSOCKET_DATA_TEXT: + data = g_bytes_unref_to_data (message, &size); + /* Convert to NULL-terminated string */ + text = g_strndup (data, size); + g_free (data); + break; + default: + g_assert_not_reached (); + } + + /* Server has accepted our registration, we are ready to send commands */ + if (g_strcmp0 (text, "HELLO") == 0) { + if (app_state != SERVER_REGISTERING) { + cleanup_and_quit_loop ("ERROR: Received HELLO when not registering", + APP_STATE_ERROR); + goto out; + } + app_state = SERVER_REGISTERED; + g_print ("Registered with server\n"); + /* Ask signalling server to connect us with a specific peer */ + if (!setup_call ()) { + cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); + goto out; + } + /* Call has been setup by the server, now we can start negotiation */ + } else if (g_strcmp0 (text, "SESSION_OK") == 0) { + if (app_state != PEER_CONNECTING) { + cleanup_and_quit_loop ("ERROR: Received SESSION_OK when not calling", + PEER_CONNECTION_ERROR); + goto out; + } + + app_state = PEER_CONNECTED; + /* Start negotiation (exchange SDP and ICE candidates) */ + if (!start_pipeline ()) + cleanup_and_quit_loop ("ERROR: failed to start pipeline", + PEER_CALL_ERROR); + /* Handle errors */ + } else if (g_str_has_prefix (text, "ERROR")) { + switch (app_state) { + case SERVER_CONNECTING: + app_state = SERVER_CONNECTION_ERROR; + break; + case SERVER_REGISTERING: + app_state = SERVER_REGISTRATION_ERROR; + break; + case PEER_CONNECTING: + app_state = PEER_CONNECTION_ERROR; + break; + case PEER_CONNECTED: + case PEER_CALL_NEGOTIATING: + app_state = PEER_CALL_ERROR; + default: + app_state = APP_STATE_ERROR; + } + cleanup_and_quit_loop (text, 0); + /* Look for JSON messages containing SDP and ICE candidates */ + } else { + JsonNode *root; + JsonObject *object; + JsonParser *parser = json_parser_new (); + if (!json_parser_load_from_data (parser, text, -1, NULL)) { + g_printerr ("Unknown message '%s', ignoring", text); + g_object_unref (parser); + goto out; + } + + root = json_parser_get_root (parser); + if (!JSON_NODE_HOLDS_OBJECT (root)) { + g_printerr ("Unknown json message '%s', ignoring", text); + g_object_unref (parser); + goto out; + } + + object = json_node_get_object (root); + /* Check type of JSON message */ + if (json_object_has_member (object, "sdp")) { + int ret; + const gchar *text; + GstSDPMessage *sdp; + GstWebRTCSessionDescription *answer; + + g_assert (app_state == PEER_CALL_NEGOTIATING); + + g_assert (json_object_has_member (object, "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 (json_object_get_string_member (object, "type"), ==, + "answer"); + + text = json_object_get_string_member (object, "sdp"); + + g_print ("Received answer:\n%s\n", text); + + ret = gst_sdp_message_new (&sdp); + g_assert (ret == GST_SDP_OK); + + ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + g_assert (ret == GST_SDP_OK); + + answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, + sdp); + g_assert (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 if (json_object_has_member (object, "ice")) { + JsonObject *ice; + const gchar *candidate; + gint sdpmlineindex; + + ice = json_object_get_object_member (object, "ice"); + candidate = json_object_get_string_member (ice, "candidate"); + sdpmlineindex = json_object_get_int_member (ice, "sdpMLineIndex"); + + /* Add ice candidate sent by remote peer */ + g_signal_emit_by_name (webrtc1, "add-ice-candidate", sdpmlineindex, + candidate); + } else { + g_printerr ("Ignoring unknown JSON message:\n%s\n", text); + } + g_object_unref (parser); + } + +out: + g_free (text); +} + +static void +on_server_connected (SoupSession * session, GAsyncResult * res, + SoupMessage *msg) +{ + GError *error = NULL; + + ws_conn = soup_session_websocket_connect_finish (session, res, &error); + if (error) { + cleanup_and_quit_loop (error->message, SERVER_CONNECTION_ERROR); + g_error_free (error); + return; + } + + g_assert (ws_conn != NULL); + + app_state = SERVER_CONNECTED; + g_print ("Connected to signalling server\n"); + + g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL); + g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL); + + /* Register with the server so it knows about us and can accept commands */ + register_with_server (); +} + +/* + * Connect to the signalling server. This is the entrypoint for everything else. + */ +static void +connect_to_websocket_server_async (void) +{ + SoupLogger *logger; + SoupMessage *message; + SoupSession *session; + const char *https_aliases[] = {"wss", NULL}; + + session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, TRUE, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + //SOUP_SESSION_SSL_CA_FILE, "/etc/ssl/certs/ca-bundle.crt", + SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); + + logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); + g_object_unref (logger); + + message = soup_message_new (SOUP_METHOD_GET, server_url); + + g_print ("Connecting to server...\n"); + + /* Once connected, we will register */ + soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, + (GAsyncReadyCallback) on_server_connected, message); + app_state = SERVER_CONNECTING; +} + +int +main (int argc, char *argv[]) +{ + SoupSession *session; + GOptionContext *context; + GError *error = NULL; + + context = g_option_context_new ("- gstreamer webrtc sendrecv demo"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gst_init_get_option_group ()); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_printerr ("Error initializing: %s\n", error->message); + return -1; + } + + if (!peer_id) { + g_printerr ("--peer-id is a required argument\n"); + return -1; + } + + loop = g_main_loop_new (NULL, FALSE); + + connect_to_websocket_server_async (); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + + gst_object_unref (pipe1); + + return 0; +} diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html new file mode 100644 index 0000000000..0a0b7fec8a --- /dev/null +++ b/webrtc/sendrecv/js/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + +
+
Status: unknown
+
Our id is unknown
+ + diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js new file mode 100644 index 0000000000..31116c017a --- /dev/null +++ b/webrtc/sendrecv/js/webrtc.js @@ -0,0 +1,203 @@ +/* vim: set sts=4 sw=4 et : + * + * Demo Javascript app for negotiating and streaming a sendrecv webrtc stream + * with a GStreamer app. Runs only in passive mode, i.e., responds to offers + * with answers, exchanges ICE candidates, and streams. + * + * Author: Nirbheek Chauhan + */ + +var connect_attempts = 0; + +var peer_connection = null; +var rtc_configuration = {iceServers: [{urls: "stun:stun.services.mozilla.com"}, + {urls: "stun:stun.l.google.com:19302"}]}; +var ws_conn; +var local_stream; +var peer_id; + +function getOurId() { + return Math.floor(Math.random() * (9000 - 10) + 10).toString(); +} + +function resetState() { + // This will call onServerClose() + ws_conn.close(); +} + +function handleIncomingError(error) { + setStatus("ERROR: " + error); + resetState(); +} + +function getVideoElement() { + return document.getElementById("stream"); +} + +function setStatus(text) { + console.log(text); + document.getElementById("status").textContent = text; +} + +function resetVideoElement() { + var videoElement = getVideoElement(); + videoElement.pause(); + videoElement.src = ""; + videoElement.load(); +} + +// SDP offer received from peer, set remote description and create an answer +function onIncomingSDP(sdp) { + console.log("Incoming SDP is "+ JSON.stringify(sdp)); + peer_connection.setRemoteDescription(sdp).then(() => { + setStatus("Remote SDP set"); + if (sdp.type != "offer") + return; + setStatus("Got SDP offer, creating answer"); + peer_connection.createAnswer().then(onLocalDescription).catch(setStatus); + }).catch(setStatus); +} + +// Local description was set, send it to peer +function onLocalDescription(desc) { + console.log("Got local description: " + JSON.stringify(desc)); + peer_connection.setLocalDescription(desc).then(function() { + setStatus("Sending SDP answer"); + ws_conn.send(JSON.stringify(peer_connection.localDescription)); + }); +} + +// ICE candidate received from peer, add it to the peer connection +function onIncomingICE(ice) { + console.log("Incoming ICE: " + JSON.stringify(ice)); + var candidate = new RTCIceCandidate(ice); + peer_connection.addIceCandidate(candidate).catch(setStatus); +} + +function onServerMessage(event) { + console.log("Received " + event.data); + switch (event.data) { + case "HELLO": + setStatus("Registered with server, waiting for call"); + return; + default: + if (event.data.startsWith("ERROR")) { + 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); + } + return; + } + + // Incoming JSON signals the beginning of a call + if (peer_connection == null) + createCall(msg); + + if (msg.sdp != null) { + onIncomingSDP(msg.sdp); + } else if (msg.ice != null) { + onIncomingICE(msg.ice); + } else { + handleIncomingError("Unknown incoming JSON: " + msg); + } + } +} + +function onServerClose(event) { + resetVideoElement(); + + if (peer_connection != null) { + peer_connection.close(); + peer_connection = null; + } + + // Reset after a second + window.setTimeout(websocketServerConnect, 1000); +} + +function onServerError(event) { + setStatus("Unable to connect to server, did you add an exception for the certificate?") + // Retry after 3 seconds + window.setTimeout(websocketServerConnect, 3000); +} + +function websocketServerConnect() { + connect_attempts++; + if (connect_attempts > 3) { + setStatus("Too many connection attempts, aborting. Refresh page to try again"); + return; + } + peer_id = getOurId(); + setStatus("Connecting to server"); + ws_conn = new WebSocket('wss://' + window.location.hostname + ':8443'); + /* When connected, immediately register with the server */ + ws_conn.addEventListener('open', (event) => { + document.getElementById("peer-id").textContent = peer_id; + ws_conn.send('HELLO ' + peer_id); + setStatus("Registering with server"); + }); + ws_conn.addEventListener('error', onServerError); + ws_conn.addEventListener('message', onServerMessage); + ws_conn.addEventListener('close', onServerClose); + + var constraints = {video: true, audio: true}; + + // Add local stream + if (navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia(constraints) + .then((stream) => { local_stream = stream }) + .catch(errorUserMediaHandler); + } else { + errorUserMediaHandler(); + } +} + +function onRemoteStreamAdded(event) { + videoTracks = event.stream.getVideoTracks(); + audioTracks = event.stream.getAudioTracks(); + + if (videoTracks.length > 0) { + console.log('Incoming stream: ' + videoTracks.length + ' video tracks and ' + audioTracks.length + ' audio tracks'); + getVideoElement().srcObject = event.stream; + } else { + handleIncomingError('Stream with unknown tracks added, resetting'); + } +} + +function errorUserMediaHandler() { + setStatus("Browser doesn't support getUserMedia!"); +} + +function createCall(msg) { + // Reset connection attempts because we connected successfully + connect_attempts = 0; + + peer_connection = new RTCPeerConnection(rtc_configuration); + peer_connection.onaddstream = onRemoteStreamAdded; + /* Send our video/audio to the other peer */ + peer_connection.addStream(local_stream); + + if (!msg.sdp) { + console.log("WARNING: First message wasn't an SDP message!?"); + } + + peer_connection.onicecandidate = (event) => { + // We have a candidate, send it to the remote party with the + // same uuid + if (event.candidate == null) { + console.log("ICE Candidate was null, done"); + return; + } + ws_conn.send(JSON.stringify({'ice': event.candidate})); + }; + + setStatus("Created peer connection for call, waiting for SDP"); +} From c2961305e3cdb96387d0d5086bcf2b321b1d1a33 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 28 Oct 2017 19:00:03 +0530 Subject: [PATCH 203/412] signalling/client.py: Rename to session-client.py Also fix CALL -> SESSION naming --- webrtc/signalling/{client.py => session-client.py} | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) rename webrtc/signalling/{client.py => session-client.py} (88%) diff --git a/webrtc/signalling/client.py b/webrtc/signalling/session-client.py similarity index 88% rename from webrtc/signalling/client.py rename to webrtc/signalling/session-client.py index 0fa8227d78..d7d0f3ed64 100755 --- a/webrtc/signalling/client.py +++ b/webrtc/signalling/session-client.py @@ -51,27 +51,29 @@ async def hello(): # Initiate call if requested if CALLEE_ID: - await ws.send('CALL {}'.format(CALLEE_ID)) + await ws.send('SESSION {}'.format(CALLEE_ID)) # Receive messages + sent_sdp = False while True: msg = await ws.recv() if msg.startswith('ERROR'): # On error, we bring down the webrtc pipeline, etc print('{!r}, exiting'.format(msg)) return + if sent_sdp: + print('Got reply sdp: ' + msg) + return # Done if CALLEE_ID: - if msg == 'CALL_OK': + if msg == 'SESSION_OK': await ws.send(send_sdp_ice()) - # Return so we don't have an infinite loop - return + sent_sdp = True else: print('Unknown reply: {!r}, exiting'.format(msg)) return else: await ws.send(reply_sdp_ice(msg)) - # Return so we don't have an infinite loop - return + return # Done print('Our uid is {!r}'.format(PEER_ID)) From 2db85c41ccee6ce3772d1e70684660f3c00eda47 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 28 Oct 2017 19:02:56 +0530 Subject: [PATCH 204/412] Protocol.md: Fix headings --- webrtc/signalling/Protocol.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webrtc/signalling/Protocol.md b/webrtc/signalling/Protocol.md index 1af892eee3..18ef5fa430 100644 --- a/webrtc/signalling/Protocol.md +++ b/webrtc/signalling/Protocol.md @@ -1,26 +1,26 @@ # Terminology -### Client +## Client A GStreamer-based application -### Browser +## Browser A JS application that runs in the browser and uses built-in browser webrtc APIs -### Peer +## Peer Any webrtc-using application that can participate in a call -### Signalling server +## Signalling server Basic websockets server implemented in Python that manages the peers list and shovels data between peers -# Overview +### Overview This is a basic protocol for doing 1-1 audio+video calls between a gstreamer app and a JS app in a browser. -# Peer registration and calling +### Peer registration and calling Peers must register with the signalling server before a call can be initiated. The server connection should stay open as long as the peer is available or in a call. @@ -38,7 +38,7 @@ This protocol builds upon https://github.com/shanet/WebRTC-Example/ * Closure of the server connection means the call has ended; either because the other peer ended it or went away * To end the call, disconnect from the server. You may reconnect again whenever you wish. -# Negotiation +### Negotiation Once a call has been setup with the signalling server, the peers must negotiate SDP and ICE candidates with each other. From d687ff3d9130cc297482b34a8f578f6627e65458 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 28 Oct 2017 19:00:42 +0530 Subject: [PATCH 205/412] simple-server: Add support for multi-party rooms Also add a new room-client.py to test the protocol which is documented in Protocol.md --- webrtc/signalling/Protocol.md | 20 ++++- webrtc/signalling/room-client.py | 107 +++++++++++++++++++++++ webrtc/signalling/simple-server.py | 135 ++++++++++++++++++++++++----- 3 files changed, 237 insertions(+), 25 deletions(-) create mode 100755 webrtc/signalling/room-client.py diff --git a/webrtc/signalling/Protocol.md b/webrtc/signalling/Protocol.md index 18ef5fa430..0aad2b8fb5 100644 --- a/webrtc/signalling/Protocol.md +++ b/webrtc/signalling/Protocol.md @@ -20,7 +20,7 @@ Basic websockets server implemented in Python that manages the peers list and sh This is a basic protocol for doing 1-1 audio+video calls between a gstreamer app and a JS app in a browser. -### Peer registration and calling +### Peer registration Peers must register with the signalling server before a call can be initiated. The server connection should stay open as long as the peer is available or in a call. @@ -31,13 +31,29 @@ This protocol builds upon https://github.com/shanet/WebRTC-Example/ * Receive `HELLO` * Any other message starting with `ERROR` is an error. -* To connect to a peer, send `SESSION ` where `` identifies the peer to connect to, and receive `SESSION_OK` +### 1-1 calls with a 'session' + +* To connect to a single peer, send `SESSION ` where `` identifies the peer to connect to, and receive `SESSION_OK` * All further messages will be forwarded to the peer * The call negotiation with the peer can be started by sending JSON encoded SDP and ICE * Closure of the server connection means the call has ended; either because the other peer ended it or went away * To end the call, disconnect from the server. You may reconnect again whenever you wish. +### Multi-party calls with a 'room' + +* To create a multi-party call, you must first register (or join) a room. Send `ROOM ` where `` is a unique room name +* Receive `ROOM_OK ` from the server if this is a new room, or `ROOM_OK ...` where `` are unique identifiers for the peers already in the room +* To send messages to a specific peer within the room for call negotiation (or any other purpose, use `ROOM_PEER_MSG ` +* When a new peer joins the room, you will receive a `ROOM_PEER_JOINED ` message + - For the purposes of convention and to avoid overwhelming newly-joined peers, offers must only be sent by the newly-joined peer +* When a peer leaves the room, you will receive a `ROOM_PEER_LEFT ` message + - You should stop sending/receiving media from/to this peer +* To get a list of all peers currently in the room, send `ROOM_PEER_LIST` and receive `ROOM_PEER_LIST ...` + - This list will never contain your own `` + - In theory you should never need to use this since you are guaranteed to receive JOINED and LEFT messages for all peers in a room +* You may stay connected to a room for as long as you like + ### Negotiation Once a call has been setup with the signalling server, the peers must negotiate SDP and ICE candidates with each other. diff --git a/webrtc/signalling/room-client.py b/webrtc/signalling/room-client.py new file mode 100755 index 0000000000..e82a3f242c --- /dev/null +++ b/webrtc/signalling/room-client.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# +# Test client for simple room-based multi-peer p2p calling +# +# Copyright (C) 2017 Centricular Ltd. +# +# Author: Nirbheek Chauhan +# + +import sys +import ssl +import json +import uuid +import asyncio +import websockets +import argparse + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--url', default='wss://localhost:8443', help='URL to connect to') +parser.add_argument('--room', default=None, help='the room to join') + +options = parser.parse_args(sys.argv[1:]) + +SERVER_ADDR = options.url +PEER_ID = 'ws-test-client-' + str(uuid.uuid4())[:6] +ROOM_ID = options.room +if ROOM_ID is None: + print('--room argument is required') + sys.exit(1) + +sslctx = False +if SERVER_ADDR.startswith(('wss://', 'https://')): + sslctx = ssl.create_default_context() + # FIXME + sslctx.check_hostname = False + sslctx.verify_mode = ssl.CERT_NONE + +def get_answer_sdp(offer, peer_id): + # Here we'd parse the incoming JSON message for ICE and SDP candidates + print("Got: " + offer) + sdp = json.dumps({'sdp': 'reply sdp'}) + answer = 'ROOM_PEER_MSG {} {}'.format(peer_id, sdp) + print("Sent: " + answer) + return answer + +def get_offer_sdp(peer_id): + sdp = json.dumps({'sdp': 'initial sdp'}) + offer = 'ROOM_PEER_MSG {} {}'.format(peer_id, sdp) + print("Sent: " + offer) + return offer + +async def hello(): + async with websockets.connect(SERVER_ADDR, ssl=sslctx) as ws: + await ws.send('HELLO ' + PEER_ID) + assert(await ws.recv() == 'HELLO') + + await ws.send('ROOM {}'.format(ROOM_ID)) + + sent_offers = set() + # Receive messages + while True: + msg = await ws.recv() + if msg.startswith('ERROR'): + # On error, we bring down the webrtc pipeline, etc + print('{!r}, exiting'.format(msg)) + return + if msg.startswith('ROOM_OK'): + print('Got ROOM_OK for room {!r}'.format(ROOM_ID)) + _, *room_peers = msg.split() + for peer_id in room_peers: + print('Sending offer to {!r}'.format(peer_id)) + # Create a peer connection for each peer and start + # exchanging SDP and ICE candidates + await ws.send(get_offer_sdp(peer_id)) + sent_offers.add(peer_id) + continue + elif msg.startswith('ROOM_PEER'): + if msg.startswith('ROOM_PEER_JOINED'): + _, peer_id = msg.split(maxsplit=1) + print('Peer {!r} joined the room'.format(peer_id)) + # Peer will send us an offer + continue + if msg.startswith('ROOM_PEER_LEFT'): + _, peer_id = msg.split(maxsplit=1) + print('Peer {!r} left the room'.format(peer_id)) + continue + elif msg.startswith('ROOM_PEER_MSG'): + _, peer_id, msg = msg.split(maxsplit=2) + if peer_id in sent_offers: + print('Got answer from {!r}: {}'.format(peer_id, msg)) + continue + print('Got offer from {!r}, replying'.format(peer_id)) + await ws.send(get_answer_sdp(msg, peer_id)) + continue + print('Unknown msg: {!r}, exiting'.format(msg)) + return + +print('Our uid is {!r}'.format(PEER_ID)) + +try: + asyncio.get_event_loop().run_until_complete(hello()) +except websockets.exceptions.InvalidHandshake: + print('Invalid handshake: are you sure this is a websockets server?\n') + raise +except ssl.SSLError: + print('SSL Error: are you sure the server is using TLS?\n') + raise diff --git a/webrtc/signalling/simple-server.py b/webrtc/signalling/simple-server.py index 34af863f06..9714213c47 100755 --- a/webrtc/signalling/simple-server.py +++ b/webrtc/signalling/simple-server.py @@ -28,12 +28,19 @@ options = parser.parse_args(sys.argv[1:]) ADDR_PORT = (options.addr, options.port) KEEPALIVE_TIMEOUT = options.keepalive_timeout -# Format: {uid: (Peer WebSocketServerProtocol, remote_address)} +############### Global data ############### + +# Format: {uid: (Peer WebSocketServerProtocol, +# remote_address, +# <'session'|room_id|None>)} peers = dict() # Format: {caller_uid: callee_uid, # callee_uid: caller_uid} # Bidirectional mapping between the two peers sessions = dict() +# Format: {room_id: {peer1_id, peer2_id, peer3_id, ...}} +# Room dict with a set of peers in each room +rooms = dict() ############### Helper functions ############### @@ -65,7 +72,7 @@ async def disconnect(ws, peer_id): # Don't care about errors asyncio.ensure_future(ws.close(reason='hangup')) -async def remove_peer(uid): +async def cleanup_session(uid): if uid in sessions: other_id = sessions[uid] del sessions[uid] @@ -77,47 +84,129 @@ async def remove_peer(uid): # close the connection to reset its state. if other_id in peers: print("Closing connection to {}".format(other_id)) - wso, oaddr = peers[other_id] + wso, oaddr, _ = peers[other_id] del peers[other_id] await wso.close() + +async def cleanup_room(uid, room_id): + room_peers = rooms[room_id] + if uid not in room_peers: + return + room_peers.remove(uid) + for pid in room_peers: + wsp, paddr, _ = peers[pid] + msg = 'ROOM_PEER_LEFT {}'.format(uid) + print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) + await wsp.send(msg) + +async def remove_peer(uid): + await cleanup_session(uid) if uid in peers: - ws, raddr = peers[uid] + ws, raddr, status = peers[uid] + if status and status != 'session': + await cleanup_room(uid, status) del peers[uid] await ws.close() print("Disconnected from peer {!r} at {!r}".format(uid, raddr)) ############### Handler functions ############### -async def connection_handler(ws, peer_id): - global peers, sessions +async def connection_handler(ws, uid): + global peers, sessions, rooms raddr = ws.remote_address - peers[peer_id] = (ws, raddr) - print("Registered peer {!r} at {!r}".format(peer_id, raddr)) + peer_status = None + peers[uid] = [ws, raddr, peer_status] + print("Registered peer {!r} at {!r}".format(uid, raddr)) while True: # Receive command, wait forever if necessary msg = await recv_msg_ping(ws, raddr) - if msg.startswith('SESSION'): - print("{!r} command {!r}".format(peer_id, msg)) + # Update current status + peer_status = peers[uid][2] + # We are in a session or a room, messages must be relayed + if peer_status is not None: + # We're in a session, route message to connected peer + if peer_status == 'session': + other_id = sessions[uid] + wso, oaddr, status = peers[other_id] + assert(status == 'session') + print("{} -> {}: {}".format(uid, other_id, msg)) + await wso.send(msg) + # We're in a room, accept room-specific commands + elif peer_status: + # ROOM_PEER_MSG peer_id MSG + if msg.startswith('ROOM_PEER_MSG'): + _, other_id, msg = msg.split(maxsplit=2) + if other_id not in peers: + await ws.send('ERROR peer {!r} not found' + ''.format(other_id)) + continue + wso, oaddr, status = peers[other_id] + if status != room_id: + await ws.send('ERROR peer {!r} is not in the room' + ''.format(other_id)) + continue + msg = 'ROOM_PEER_MSG {} {}'.format(uid, msg) + print('room {}: {} -> {}: {}'.format(room_id, uid, other_id, msg)) + await wso.send(msg) + elif msg == 'ROOM_PEER_LIST': + room_id = peers[peer_id][2] + room_peers = ' '.join([pid for pid in rooms[room_id] if pid != peer_id]) + msg = 'ROOM_PEER_LIST {}'.format(room_peers) + print('room {}: -> {}: {}'.format(room_id, uid, msg)) + await ws.send(msg) + else: + await ws.send('ERROR invalid msg, already in room') + continue + else: + raise AssertionError('Unknown peer status {!r}'.format(peer_status)) + # Requested a session with a specific peer + elif msg.startswith('SESSION'): + print("{!r} command {!r}".format(uid, msg)) _, callee_id = msg.split(maxsplit=1) if callee_id not in peers: await ws.send('ERROR peer {!r} not found'.format(callee_id)) continue - if callee_id in sessions: + if peer_status is not None: await ws.send('ERROR peer {!r} busy'.format(callee_id)) continue await ws.send('SESSION_OK') wsc = peers[callee_id][0] - print("Session from {!r} ({!r}) to {!r} ({!r})".format(peer_id, raddr, callee_id, - wsc.remote_address)) - # Register call - sessions[peer_id] = callee_id - sessions[callee_id] = peer_id - # We're in a session, route message to connected peer - elif peer_id in sessions: - other_id = sessions[peer_id] - wso, oaddr = peers[other_id] - print("{} -> {}: {}".format(peer_id, other_id, msg)) - await wso.send(msg) + print('Session from {!r} ({!r}) to {!r} ({!r})' + ''.format(uid, raddr, callee_id, wsc.remote_address)) + # Register session + peers[uid][2] = peer_status = 'session' + sessions[uid] = callee_id + peers[callee_id][2] = 'session' + sessions[callee_id] = uid + # Requested joining or creation of a room + elif msg.startswith('ROOM'): + print('{!r} command {!r}'.format(uid, msg)) + _, room_id = msg.split(maxsplit=1) + # Room name cannot be 'session', empty, or contain whitespace + if room_id == 'session' or room_id.split() != [room_id]: + await ws.send('ERROR invalid room id {!r}'.format(room_id)) + continue + if room_id in rooms: + if uid in rooms[room_id]: + raise AssertionError('How did we accept a ROOM command ' + 'despite already being in a room?') + else: + # Create room if required + rooms[room_id] = set() + room_peers = ' '.join([pid for pid in rooms[room_id]]) + await ws.send('ROOM_OK {}'.format(room_peers)) + # Enter room + peers[uid][2] = peer_status = room_id + rooms[room_id].add(uid) + for pid in rooms[room_id]: + if pid == uid: + continue + wsp, paddr, _ = peers[pid] + msg = 'ROOM_PEER_JOINED {}'.format(uid) + print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) + await wsp.send(msg) + else: + print('Ignoring unknown message {!r} from {!r}'.format(msg, uid)) async def hello_peer(ws): ''' @@ -129,7 +218,7 @@ async def hello_peer(ws): if hello != 'HELLO': await ws.close(code=1002, reason='invalid protocol') raise Exception("Invalid hello from {!r}".format(raddr)) - if not uid or uid in peers: + if not uid or uid in peers or uid.split() != [uid]: # no whitespace await ws.close(code=1002, reason='invalid peer uid') raise Exception("Invalid uid {!r} from {!r}".format(uid, raddr)) # Send back a HELLO From 96e4f39fd887c189f8b804f4e5b04552890c57be Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 29 Oct 2017 04:08:45 +0530 Subject: [PATCH 206/412] Update Protocol.md Fix indentation typos --- webrtc/signalling/Protocol.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/signalling/Protocol.md b/webrtc/signalling/Protocol.md index 0aad2b8fb5..2772d5500a 100644 --- a/webrtc/signalling/Protocol.md +++ b/webrtc/signalling/Protocol.md @@ -48,10 +48,10 @@ This protocol builds upon https://github.com/shanet/WebRTC-Example/ * When a new peer joins the room, you will receive a `ROOM_PEER_JOINED ` message - For the purposes of convention and to avoid overwhelming newly-joined peers, offers must only be sent by the newly-joined peer * When a peer leaves the room, you will receive a `ROOM_PEER_LEFT ` message - - You should stop sending/receiving media from/to this peer + - You should stop sending/receiving media from/to this peer * To get a list of all peers currently in the room, send `ROOM_PEER_LIST` and receive `ROOM_PEER_LIST ...` - - This list will never contain your own `` - - In theory you should never need to use this since you are guaranteed to receive JOINED and LEFT messages for all peers in a room + - This list will never contain your own `` + - In theory you should never need to use this since you are guaranteed to receive JOINED and LEFT messages for all peers in a room * You may stay connected to a room for as long as you like ### Negotiation From 569aff43f9e80f95eb6ce6e7de190b9664775b00 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 30 Oct 2017 09:12:06 +0530 Subject: [PATCH 207/412] sendrecv: Rename function for greater clarity --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 8c25f97cfb..3ee0e44789 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -216,7 +216,7 @@ send_sdp_offer (GstWebRTCSessionDescription * offer) /* Offer created by our pipeline, to be sent to the peer */ static void -on_offer_received (GstPromise * promise, gpointer user_data) +on_offer_created (GstPromise * promise, gpointer user_data) { GstWebRTCSessionDescription *offer = NULL; gchar *desc; @@ -245,7 +245,7 @@ on_negotiation_needed (GstElement * element, gpointer user_data) app_state = PEER_CALL_NEGOTIATING; g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); - gst_promise_set_change_callback (promise, on_offer_received, user_data, + gst_promise_set_change_callback (promise, on_offer_created, user_data, NULL); } From 9b1a0e538992444b292f8bbca38bbe4a7da4c7ab Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 30 Oct 2017 09:09:36 +0530 Subject: [PATCH 208/412] WIP: Add a new multiparty sendrecv gstreamer demo You can join a room and an audio-only call will be started with all peers in that room. Currently uses audiotestsrc only. BUG: With >2 peers in a call, if a peer leaves, the pipeline stops outputting data from the remaining peers to the (audio) sink. TODO: JS code to allow a browser to join the call TODO: Cleanup pipeline when a peer leaves TODO: Add ICE servers to allow calls over the Internet TODO: Perhaps setup a TURN server as well --- webrtc/multiparty-sendrecv/gst/.gitignore | 1 + .../gst/mp-webrtc-sendrecv.c | 889 ++++++++++++++++++ 2 files changed, 890 insertions(+) create mode 100644 webrtc/multiparty-sendrecv/gst/.gitignore create mode 100644 webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c diff --git a/webrtc/multiparty-sendrecv/gst/.gitignore b/webrtc/multiparty-sendrecv/gst/.gitignore new file mode 100644 index 0000000000..e1b42bed77 --- /dev/null +++ b/webrtc/multiparty-sendrecv/gst/.gitignore @@ -0,0 +1 @@ +mp-webrtc-sendrecv diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c new file mode 100644 index 0000000000..ed25f117c4 --- /dev/null +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -0,0 +1,889 @@ +/* + * Demo gstreamer app for negotiating and streaming a sendrecv audio-only webrtc + * stream to all the peers in a multiparty room. + * + * gcc mp-webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o mp-webrtc-sendrecv + * + * Author: Nirbheek Chauhan + */ +#include +#include +#include + +/* For signalling */ +#include +#include + +#include + +enum AppState { + APP_STATE_UNKNOWN = 0, + APP_STATE_ERROR = 1, /* generic error */ + SERVER_CONNECTING = 1000, + SERVER_CONNECTION_ERROR, + SERVER_CONNECTED, /* Ready to register */ + SERVER_REGISTERING = 2000, + SERVER_REGISTRATION_ERROR, + SERVER_REGISTERED, /* Ready to call a peer */ + SERVER_CLOSED, /* server connection closed by us or the server */ + ROOM_JOINING = 3000, + ROOM_JOIN_ERROR, + ROOM_JOINED, + ROOM_CALL_NEGOTIATING = 4000, /* negotiating with some or all peers */ + ROOM_CALL_OFFERING, /* when we're the one sending the offer */ + ROOM_CALL_ANSWERING, /* when we're the one answering an offer */ + ROOM_CALL_STARTED, /* in a call with some or all peers */ + ROOM_CALL_STOPPING, + ROOM_CALL_STOPPED, + ROOM_CALL_ERROR, +}; + +static GMainLoop *loop; +static GstElement *pipeline; +static GList *peers; + +static SoupWebsocketConnection *ws_conn = NULL; +static enum AppState app_state = 0; +static const gchar *default_server_url = "wss://webrtc.nirbheek.in:8443"; +static gchar *server_url = NULL; +static gchar *local_id = NULL; +static gchar *room_id = NULL; + +static GOptionEntry entries[] = +{ + { "name", 0, 0, G_OPTION_ARG_STRING, &local_id, "Name we will send to the server", "ID" }, + { "room-id", 0, 0, G_OPTION_ARG_STRING, &room_id, "Room name to join or create", "ID" }, + { "server", 0, 0, G_OPTION_ARG_STRING, &server_url, "Signalling server to connect to", "URL" }, +}; + +static gint +compare_str_glist (gconstpointer a, gconstpointer b) +{ + return g_strcmp0 (a, b); +} + +static const gchar * +find_peer_from_list (const gchar * peer_id) +{ + return (g_list_find_custom (peers, peer_id, compare_str_glist))->data; +} + +static gboolean +cleanup_and_quit_loop (const gchar * msg, enum AppState state) +{ + if (msg) + g_printerr ("%s\n", msg); + if (state > 0) + app_state = state; + + if (ws_conn) { + if (soup_websocket_connection_get_state (ws_conn) == + SOUP_WEBSOCKET_STATE_OPEN) + /* This will call us again */ + soup_websocket_connection_close (ws_conn, 1000, ""); + else + g_object_unref (ws_conn); + } + + if (loop) { + g_main_loop_quit (loop); + loop = NULL; + } + + /* To allow usage as a GSourceFunc */ + return G_SOURCE_REMOVE; +} + +static gchar* +get_string_from_json_object (JsonObject * object) +{ + JsonNode *root; + JsonGenerator *generator; + gchar *text; + + /* Make it the root node */ + root = json_node_init_object (json_node_alloc (), object); + generator = json_generator_new (); + json_generator_set_root (generator, root); + text = json_generator_to_data (generator, NULL); + + /* Release everything */ + g_object_unref (generator); + json_node_free (root); + return text; +} + +static void +handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, + const char * sink_name) +{ + GstPad *qpad; + GstElement *q, *conv, *sink; + GstPadLinkReturn ret; + + q = gst_element_factory_make ("queue", NULL); + g_assert_nonnull (q); + conv = gst_element_factory_make (convert_name, NULL); + g_assert_nonnull (conv); + sink = gst_element_factory_make (sink_name, NULL); + g_assert_nonnull (sink); + gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, sink, NULL); + + qpad = gst_element_get_static_pad (q, "sink"); + + ret = gst_pad_link (pad, qpad); + g_assert_cmpint (ret, ==, GST_PAD_LINK_OK); +} + +static void +on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, + GstElement * pipe) +{ + GstCaps *caps; + const gchar *name; + + if (!gst_pad_has_current_caps (pad)) { + g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + GST_PAD_NAME (pad)); + return; + } + + caps = gst_pad_get_current_caps (pad); + name = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + + if (g_str_has_prefix (name, "video")) { + handle_media_stream (pad, pipe, "videoconvert", "autovideosink"); + } else if (g_str_has_prefix (name, "audio")) { + handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); + } else { + g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + } +} + +static void +on_incoming_stream (GstElement * webrtc, GstPad * pad, GstElement * pipe) +{ + GstElement *decodebin; + + if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) + return; + + decodebin = gst_element_factory_make ("decodebin", NULL); + g_signal_connect (decodebin, "pad-added", + G_CALLBACK (on_incoming_decodebin_stream), pipe); + gst_bin_add (GST_BIN (pipe), decodebin); + gst_element_sync_state_with_parent (decodebin); + gst_element_link (webrtc, decodebin); +} + +static void +send_room_peer_msg (const gchar * text, const gchar * peer_id) +{ + gchar *msg; + + msg = g_strdup_printf ("ROOM_PEER_MSG %s %s", peer_id, text); + soup_websocket_connection_send_text (ws_conn, msg); + g_free (msg); +} + +static void +send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex, + gchar * candidate, const gchar * peer_id) +{ + gchar *text; + JsonObject *ice, *msg; + + if (app_state < ROOM_CALL_OFFERING) { + cleanup_and_quit_loop ("Can't send ICE, not in call", APP_STATE_ERROR); + return; + } + + 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); + + send_room_peer_msg (text, peer_id); + g_free (text); +} + +static void +send_room_peer_sdp (GstWebRTCSessionDescription * desc, const gchar * peer_id) +{ + JsonObject *msg, *sdp; + gchar *text, *sdptype, *sdptext; + + g_assert_cmpint (app_state, <, ROOM_CALL_OFFERING); + + if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) + sdptype = "offer"; + else if (desc->type == GST_WEBRTC_SDP_TYPE_ANSWER) + sdptype = "answer"; + else + g_assert_not_reached (); + + text = gst_sdp_message_as_text (desc->sdp); + g_print ("Sending sdp %s to %s:\n%s\n", sdptype, peer_id, text); + + sdp = json_object_new (); + json_object_set_string_member (sdp, "type", sdptype); + json_object_set_string_member (sdp, "sdp", text); + g_free (text); + + msg = json_object_new (); + json_object_set_object_member (msg, "sdp", sdp); + sdptext = get_string_from_json_object (msg); + json_object_unref (msg); + + send_room_peer_msg (sdptext, peer_id); + g_free (sdptext); +} + +/* Offer created by our pipeline, to be sent to the peer */ +static void +on_offer_created (GstPromise * promise, const gchar * peer_id) +{ + GstElement *webrtc; + GstWebRTCSessionDescription *offer; + + g_assert_cmpint (app_state, ==, ROOM_CALL_OFFERING); + + g_assert_cmpint (promise->result, ==, GST_PROMISE_RESULT_REPLIED); + gst_structure_get (promise->promise, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + gst_promise_unref (promise); + + promise = gst_promise_new (); + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + g_assert_nonnull (webrtc); + g_signal_emit_by_name (webrtc, "set-local-description", offer, promise); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + + /* Send offer to peer */ + send_room_peer_sdp (offer, peer_id); + gst_webrtc_session_description_free (offer); +} + +static void +on_negotiation_needed (GstElement * webrtc, const gchar * peer_id) +{ + GstPromise *promise = gst_promise_new (); + + app_state = ROOM_CALL_OFFERING; + g_signal_emit_by_name (webrtc, "create-offer", NULL, promise); + gst_promise_set_change_callback (promise, + (GstPromiseChangeFunc) on_offer_created, (gpointer) peer_id, NULL); +} + +static void +add_webrtcbin_to_pipeline (const gchar * peer_id, gboolean offer) +{ + int ret; + GstElement *tee, *webrtc; + GstPad *srcpad, *sinkpad; + + webrtc = gst_element_factory_make ("webrtcbin", peer_id); + g_assert_nonnull (webrtc); + + tee = gst_bin_get_by_name (GST_BIN (pipeline), "audiotee"); + g_assert_nonnull (tee); + srcpad = gst_element_get_request_pad (tee, "src_%u"); + gst_object_unref (tee); + sinkpad = gst_element_get_request_pad (webrtc, "sink_%u"); + + /* Add the bin to the pipeline and connect it */ + gst_bin_add (GST_BIN (pipeline), webrtc); + + ret = gst_pad_link (srcpad, sinkpad); + g_assert_cmpint (ret, ==, GST_PAD_LINK_OK); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + /* This is the gstwebrtc entry point where we create the offer and so on. It + * will be called when the pipeline goes to PLAYING. + * XXX: We must connect this after webrtcbin has been linked to a source via + * get_request_pad() otherwise webrtcbin will create an SDP offer with no + * media lines in it. */ + if (offer) + g_signal_connect (webrtc, "on-negotiation-needed", + G_CALLBACK (on_negotiation_needed), (gpointer) peer_id); + + /* We need to transmit this ICE candidate to the browser via the websockets + * signalling server. Incoming ice candidates from the browser need to be + * added by us too, see on_server_message() */ + g_signal_connect (webrtc, "on-ice-candidate", + G_CALLBACK (send_ice_candidate_message), (gpointer) peer_id); + /* Incoming streams will be exposed via this signal */ + g_signal_connect (webrtc, "pad-added", G_CALLBACK (on_incoming_stream), + pipeline); + + /* Set to bin to PLAYING */ + ret = gst_element_sync_state_with_parent (webrtc); + gst_object_unref (webrtc); + g_assert_true (ret); +} + +static void +call_peer (const gchar * peer_id) +{ + add_webrtcbin_to_pipeline (peer_id, TRUE); +} + +static void +incoming_call_from_peer (const gchar * peer_id) +{ + add_webrtcbin_to_pipeline (peer_id, FALSE); +} + +#define STR(x) #x +#define RTP_CAPS_OPUS(x) "application/x-rtp,media=audio,encoding-name=OPUS,payload=" STR(x) +#define RTP_CAPS_VP8(x) "application/x-rtp,media=video,encoding-name=VP8,payload=" STR(x) + +static gboolean +start_pipeline (void) +{ + GstStateChangeReturn ret; + GError *error = NULL; + + /* NOTE: webrtcbin currently does not support dynamic addition/removal of + * streams, so we use a separate webrtcbin for each peer, but all of them are + * inside the same pipeline. We start by connecting it to a fakesink so that + * we can preroll early. */ + pipeline = gst_parse_launch ("tee name=audiotee ! queue ! fakesink " + "audiotestsrc is-live=true wave=red-noise ! queue ! opusenc ! rtpopuspay ! " + "queue ! " RTP_CAPS_OPUS(96) " ! audiotee. ", + &error); + + if (error) { + g_printerr ("Failed to parse launch: %s\n", error->message); + g_error_free (error); + goto err; + } + + g_print ("Starting pipeline, not transmitting yet\n"); + ret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + goto err; + + return TRUE; + +err: + if (pipeline) + g_clear_object (&pipeline); + return FALSE; +} + +static gboolean +join_room_on_server (void) +{ + gchar *msg; + + if (soup_websocket_connection_get_state (ws_conn) != + SOUP_WEBSOCKET_STATE_OPEN) + return FALSE; + + if (!room_id) + return FALSE; + + g_print ("Joining room %s\n", room_id); + app_state = ROOM_JOINING; + msg = g_strdup_printf ("ROOM %s", room_id); + soup_websocket_connection_send_text (ws_conn, msg); + g_free (msg); + return TRUE; +} + +static gboolean +register_with_server (void) +{ + gchar *hello; + + if (soup_websocket_connection_get_state (ws_conn) != + SOUP_WEBSOCKET_STATE_OPEN) + return FALSE; + + g_print ("Registering id %s with server\n", local_id); + app_state = SERVER_REGISTERING; + + /* Register with the server with a random integer id. Reply will be received + * by on_server_message() */ + hello = g_strdup_printf ("HELLO %s", local_id); + soup_websocket_connection_send_text (ws_conn, hello); + g_free (hello); + + return TRUE; +} + +static void +on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + app_state = SERVER_CLOSED; + cleanup_and_quit_loop ("Server connection closed", 0); +} + +static gboolean +do_registration (void) +{ + if (app_state != SERVER_REGISTERING) { + cleanup_and_quit_loop ("ERROR: Received HELLO when not registering", + APP_STATE_ERROR); + return FALSE; + } + app_state = SERVER_REGISTERED; + g_print ("Registered with server\n"); + /* Ask signalling server that we want to join a room */ + if (!join_room_on_server ()) { + cleanup_and_quit_loop ("ERROR: Failed to join room", ROOM_CALL_ERROR); + return FALSE; + } + return TRUE; +} + +/* + * When we join a room, we are responsible for calling by starting negotiation + * with each peer in it by sending an SDP offer and ICE candidates. + */ +static void +do_join_room (const gchar * text) +{ + gint ii, len; + gchar **peer_ids; + + if (app_state != ROOM_JOINING) { + cleanup_and_quit_loop ("ERROR: Received ROOM_OK when not calling", + ROOM_JOIN_ERROR); + return; + } + + app_state = ROOM_JOINED; + /* Start recording, but not transmitting */ + if (!start_pipeline ()) { + cleanup_and_quit_loop ("ERROR: Failed to start pipeline", + ROOM_CALL_ERROR); + return; + } + + peer_ids = g_strsplit (text, " ", -1); + g_assert_cmpstr (peer_ids[0], ==, "ROOM_OK"); + len = g_strv_length (peer_ids); + /* There are peers in the room already. We need to start negotiation + * (exchange SDP and ICE candidates) and transmission of media. */ + if (len > 1 && strlen (peer_ids[1]) > 0) { + g_print ("Found %i peers already in room\n", len - 1); + app_state = ROOM_CALL_OFFERING; + for (ii = 1; ii < len; ii++) { + gchar *peer_id = g_strdup (peer_ids[ii]); + g_print ("Negotiating with peer %s\n", peer_id); + /* This might fail asynchronously */ + call_peer (peer_id); + peers = g_list_prepend (peers, peer_id); + } + } + + g_strfreev (peer_ids); + return; +} + +static void +handle_error_message (const gchar * msg) +{ + switch (app_state) { + case SERVER_CONNECTING: + app_state = SERVER_CONNECTION_ERROR; + break; + case SERVER_REGISTERING: + app_state = SERVER_REGISTRATION_ERROR; + break; + case ROOM_JOINING: + app_state = ROOM_JOIN_ERROR; + break; + case ROOM_JOINED: + case ROOM_CALL_NEGOTIATING: + case ROOM_CALL_OFFERING: + case ROOM_CALL_ANSWERING: + app_state = ROOM_CALL_ERROR; + break; + case ROOM_CALL_STARTED: + case ROOM_CALL_STOPPING: + case ROOM_CALL_STOPPED: + app_state = ROOM_CALL_ERROR; + break; + default: + app_state = APP_STATE_ERROR; + } + cleanup_and_quit_loop (msg, 0); +} + +static void +on_answer_created (GstPromise * promise, const gchar * peer_id) +{ + GstElement *webrtc; + GstWebRTCSessionDescription *answer; + + g_assert_cmpint (app_state, ==, ROOM_CALL_ANSWERING); + + g_assert_cmpint (promise->result, ==, GST_PROMISE_RESULT_REPLIED); + gst_structure_get (promise->promise, "answer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL); + gst_promise_unref (promise); + + promise = gst_promise_new (); + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + g_assert_nonnull (webrtc); + g_signal_emit_by_name (webrtc, "set-local-description", answer, promise); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + + /* Send offer to peer */ + send_room_peer_sdp (answer, peer_id); + gst_webrtc_session_description_free (answer); + + app_state = ROOM_CALL_STARTED; +} + +static void +handle_sdp_offer (const gchar * peer_id, const gchar * text) +{ + int ret; + GstPromise *promise; + GstElement *webrtc; + GstSDPMessage *sdp; + GstWebRTCSessionDescription *offer; + + g_assert_cmpint (app_state, ==, ROOM_CALL_ANSWERING); + + g_print ("Received offer:\n%s\n", text); + + ret = gst_sdp_message_new (&sdp); + g_assert_cmpint (ret, ==, GST_SDP_OK); + + ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + g_assert_cmpint (ret, ==, GST_SDP_OK); + + 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 (); + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + g_assert_nonnull (webrtc); + g_signal_emit_by_name (webrtc, "set-remote-description", offer, promise); + /* We don't want to be notified when the action is done */ + gst_promise_interrupt (promise); + gst_promise_unref (promise); + + /* Create an answer that we will send back to the peer */ + promise = gst_promise_new (); + gst_promise_set_change_callback (promise, + (GstPromiseChangeFunc) on_answer_created, (gpointer) peer_id, NULL); + g_signal_emit_by_name (webrtc, "create-answer", NULL, promise); + + gst_webrtc_session_description_free (offer); + gst_object_unref (webrtc); +} + +static void +handle_sdp_answer (const gchar * peer_id, const gchar * text) +{ + int ret; + GstPromise *promise; + GstElement *webrtc; + GstSDPMessage *sdp; + GstWebRTCSessionDescription *answer; + + g_assert_cmpint (app_state, >=, ROOM_CALL_OFFERING); + + g_print ("Received answer:\n%s\n", text); + + ret = gst_sdp_message_new (&sdp); + g_assert_cmpint (ret, ==, GST_SDP_OK); + + ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + g_assert_cmpint (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 */ + promise = gst_promise_new (); + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + g_assert_nonnull (webrtc); + g_signal_emit_by_name (webrtc, "set-remote-description", answer, promise); + gst_object_unref (webrtc); + /* We don't want to be notified when the action is done */ + gst_promise_interrupt (promise); + gst_promise_unref (promise); +} + +static gboolean +handle_peer_message (const gchar * peer_id, const gchar * msg) +{ + JsonNode *root; + JsonObject *object, *child; + JsonParser *parser = json_parser_new (); + if (!json_parser_load_from_data (parser, msg, -1, NULL)) { + g_printerr ("Unknown message '%s' from '%s', ignoring", msg, peer_id); + g_object_unref (parser); + return FALSE; + } + + root = json_parser_get_root (parser); + if (!JSON_NODE_HOLDS_OBJECT (root)) { + g_printerr ("Unknown json message '%s' from '%s', ignoring", msg, peer_id); + g_object_unref (parser); + return FALSE; + } + + g_print ("Message from peer %s: %s\n", peer_id, msg); + + object = json_node_get_object (root); + /* Check type of JSON message */ + if (json_object_has_member (object, "sdp")) { + int ret; + GstSDPMessage *sdp; + const gchar *text, *sdp_type; + GstWebRTCSessionDescription *answer; + + g_assert_cmpint (app_state, >=, ROOM_JOINED); + + child = json_object_get_object_member (object, "sdp"); + + if (!json_object_has_member (child, "type")) { + cleanup_and_quit_loop ("ERROR: received SDP without 'type'", + ROOM_CALL_ERROR); + return FALSE; + } + + sdp_type = json_object_get_string_member (child, "type"); + text = json_object_get_string_member (child, "sdp"); + + if (g_strcmp0 (sdp_type, "offer") == 0) { + app_state = ROOM_CALL_ANSWERING; + incoming_call_from_peer (peer_id); + handle_sdp_offer (peer_id, text); + } else if (g_strcmp0 (sdp_type, "answer") == 0) { + g_assert_cmpint (app_state, >=, ROOM_CALL_OFFERING); + handle_sdp_answer (peer_id, text); + app_state = ROOM_CALL_STARTED; + } else { + cleanup_and_quit_loop ("ERROR: invalid sdp_type", ROOM_CALL_ERROR); + return FALSE; + } + } else if (json_object_has_member (object, "ice")) { + GstElement *webrtc; + const gchar *candidate; + gint sdpmlineindex; + + child = json_object_get_object_member (object, "ice"); + candidate = json_object_get_string_member (child, "candidate"); + sdpmlineindex = json_object_get_int_member (child, "sdpMLineIndex"); + + /* Add ice candidate sent by remote peer */ + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + g_assert_nonnull (webrtc); + g_signal_emit_by_name (webrtc, "add-ice-candidate", sdpmlineindex, + candidate); + gst_object_unref (webrtc); + } else { + g_printerr ("Ignoring unknown JSON message:\n%s\n", msg); + } + g_object_unref (parser); + return TRUE; +} + +/* One mega message handler for our asynchronous calling mechanism */ +static void +on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, + GBytes * message, gpointer user_data) +{ + gsize size; + gchar *text, *data; + + switch (type) { + case SOUP_WEBSOCKET_DATA_BINARY: + g_printerr ("Received unknown binary message, ignoring\n"); + g_bytes_unref (message); + return; + case SOUP_WEBSOCKET_DATA_TEXT: + data = g_bytes_unref_to_data (message, &size); + /* Convert to NULL-terminated string */ + text = g_strndup (data, size); + g_free (data); + break; + default: + g_assert_not_reached (); + } + + /* Server has accepted our registration, we are ready to send commands */ + if (g_strcmp0 (text, "HELLO") == 0) { + /* May fail asynchronously */ + do_registration (); + /* Room-related message */ + } else if (g_str_has_prefix (text, "ROOM_")) { + /* Room joined, now we can start negotiation */ + if (g_str_has_prefix (text, "ROOM_OK ")) { + /* May fail asynchronously */ + do_join_room (text); + } else if (g_str_has_prefix (text, "ROOM_PEER")) { + gchar **splitm = NULL; + const gchar *peer_id; + /* SDP and ICE, usually */ + if (g_str_has_prefix (text, "ROOM_PEER_MSG")) { + splitm = g_strsplit (text, " ", 3); + peer_id = find_peer_from_list (splitm[1]); + g_assert_nonnull (peer_id); + /* Could be an offer or an answer, or ICE, or an arbitrary message */ + handle_peer_message (peer_id, splitm[2]); + } else if (g_str_has_prefix (text, "ROOM_PEER_JOINED")) { + splitm = g_strsplit (text, " ", 2); + peers = g_list_prepend (peers, g_strdup (splitm[1])); + peer_id = find_peer_from_list (splitm[1]); + g_assert_nonnull (peer_id); + g_print ("Peer %s has joined the room\n", peer_id); + } else if (g_str_has_prefix (text, "ROOM_PEER_LEFT")) { + splitm = g_strsplit (text, " ", 2); + peer_id = find_peer_from_list (splitm[1]); + g_assert_nonnull (peer_id); + peers = g_list_remove (peers, peer_id); + g_print ("Peer %s has left the room\n", peer_id); + g_free ((gchar*) peer_id); + /* TODO: cleanup pipeline */ + } else { + g_printerr ("WARNING: Ignoring unknown message %s\n", text); + } + g_strfreev (splitm); + } else { + goto err; + } + /* Handle errors */ + } else if (g_str_has_prefix (text, "ERROR")) { + handle_error_message (text); + } else { + goto err; + } + +out: + g_free (text); + return; + +err: + { + gchar *err_s = g_strdup_printf ("ERROR: unknown message %s", text); + cleanup_and_quit_loop (err_s, 0); + g_free (err_s); + goto out; + } +} + +static void +on_server_connected (SoupSession * session, GAsyncResult * res, + SoupMessage *msg) +{ + GError *error = NULL; + + ws_conn = soup_session_websocket_connect_finish (session, res, &error); + if (error) { + cleanup_and_quit_loop (error->message, SERVER_CONNECTION_ERROR); + g_error_free (error); + return; + } + + g_assert_nonnull (ws_conn); + + app_state = SERVER_CONNECTED; + g_print ("Connected to signalling server\n"); + + g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL); + g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL); + + /* Register with the server so it knows about us and can accept commands + * responses from the server will be handled in on_server_message() above */ + register_with_server (); +} + +/* + * Connect to the signalling server. This is the entrypoint for everything else. + */ +static void +connect_to_websocket_server_async (void) +{ + SoupLogger *logger; + SoupMessage *message; + SoupSession *session; + const char *https_aliases[] = {"wss", NULL}; + + session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, TRUE, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + //SOUP_SESSION_SSL_CA_FILE, "/etc/ssl/certs/ca-bundle.crt", + SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); + + logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); + g_object_unref (logger); + + message = soup_message_new (SOUP_METHOD_GET, server_url); + + g_print ("Connecting to server...\n"); + + /* Once connected, we will register */ + soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, + (GAsyncReadyCallback) on_server_connected, message); + app_state = SERVER_CONNECTING; +} + +int +main (int argc, char *argv[]) +{ + SoupSession *session; + GOptionContext *context; + GError *error = NULL; + + context = g_option_context_new ("- gstreamer webrtc sendrecv demo"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gst_init_get_option_group ()); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_printerr ("Error initializing: %s\n", error->message); + return -1; + } + + if (!room_id) { + g_printerr ("--room-id is a required argument\n"); + return -1; + } + + if (!local_id) + local_id = g_strdup_printf ("%s-%i", g_get_user_name (), + g_random_int_range (10, 10000)); + /* Sanitize by removing whitespace, modifies string in-place */ + g_strdelimit (local_id, " \t\n\r", '-'); + + g_print ("Our local id is %s\n", local_id); + + if (!server_url) + server_url = g_strdup (default_server_url); + + loop = g_main_loop_new (NULL, FALSE); + + connect_to_websocket_server_async (); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + + gst_object_unref (pipeline); + g_free (server_url); + g_free (local_id); + g_free (room_id); + + return 0; +} From 0c5e7999526cbdcfc2ccc82da4f637bc4dd37040 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 30 Oct 2017 13:24:21 +0530 Subject: [PATCH 209/412] multiparty sendrecv: Add a queue before the audio sink Missed this, fixes the bug where removing a peer causes the pipeline to get stuck. However, when peers leave, there is still a chance that the pipeline will get stuck. --- .../gst/mp-webrtc-sendrecv.c | 79 +++++++++++++++---- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index ed25f117c4..184c02954e 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -220,7 +220,7 @@ send_room_peer_sdp (GstWebRTCSessionDescription * desc, const gchar * peer_id) JsonObject *msg, *sdp; gchar *text, *sdptype, *sdptext; - g_assert_cmpint (app_state, <, ROOM_CALL_OFFERING); + g_assert_cmpint (app_state, >=, ROOM_CALL_OFFERING); if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) sdptype = "offer"; @@ -284,24 +284,71 @@ on_negotiation_needed (GstElement * webrtc, const gchar * peer_id) } static void -add_webrtcbin_to_pipeline (const gchar * peer_id, gboolean offer) +remove_peer_from_pipeline (const gchar * peer_id) +{ + gchar *qname; + GstPad *srcpad, *sinkpad; + GstElement *webrtc, *q, *tee; + + webrtc = gst_bin_get_by_name (GST_BIN (pipeline), peer_id); + if (!webrtc) + return; + + gst_bin_remove (GST_BIN (pipeline), webrtc); + gst_object_unref (webrtc); + + qname = g_strdup_printf ("queue-%s", peer_id); + q = gst_bin_get_by_name (GST_BIN (pipeline), qname); + g_free (qname); + + sinkpad = gst_element_get_static_pad (q, "sink"); + g_assert_nonnull (sinkpad); + srcpad = gst_pad_get_peer (sinkpad); + g_assert_nonnull (srcpad); + gst_object_unref (sinkpad); + + gst_bin_remove (GST_BIN (pipeline), q); + gst_object_unref (q); + + tee = gst_bin_get_by_name (GST_BIN (pipeline), "audiotee"); + g_assert_nonnull (tee); + gst_element_release_request_pad (tee, srcpad); + gst_object_unref (srcpad); + gst_object_unref (tee); +} + +static void +add_peer_to_pipeline (const gchar * peer_id, gboolean offer) { int ret; - GstElement *tee, *webrtc; + gchar *tmp; + GstElement *tee, *webrtc, *q; GstPad *srcpad, *sinkpad; + GError *error = NULL; + tmp = g_strdup_printf ("queue-%s", peer_id); + q = gst_element_factory_make ("queue", tmp); + g_free (tmp); webrtc = gst_element_factory_make ("webrtcbin", peer_id); - g_assert_nonnull (webrtc); + + gst_bin_add_many (GST_BIN (pipeline), q, webrtc, NULL); + + srcpad = gst_element_get_static_pad (q, "src"); + g_assert_nonnull (srcpad); + sinkpad = gst_element_get_request_pad (webrtc, "sink_%u"); + g_assert_nonnull (sinkpad); + ret = gst_pad_link (srcpad, sinkpad); + g_assert_cmpint (ret, ==, GST_PAD_LINK_OK); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); tee = gst_bin_get_by_name (GST_BIN (pipeline), "audiotee"); g_assert_nonnull (tee); srcpad = gst_element_get_request_pad (tee, "src_%u"); + g_assert_nonnull (srcpad); gst_object_unref (tee); - sinkpad = gst_element_get_request_pad (webrtc, "sink_%u"); - - /* Add the bin to the pipeline and connect it */ - gst_bin_add (GST_BIN (pipeline), webrtc); - + sinkpad = gst_element_get_static_pad (q, "sink"); + g_assert_nonnull (sinkpad); ret = gst_pad_link (srcpad, sinkpad); g_assert_cmpint (ret, ==, GST_PAD_LINK_OK); gst_object_unref (srcpad); @@ -310,8 +357,8 @@ add_webrtcbin_to_pipeline (const gchar * peer_id, gboolean offer) /* This is the gstwebrtc entry point where we create the offer and so on. It * will be called when the pipeline goes to PLAYING. * XXX: We must connect this after webrtcbin has been linked to a source via - * get_request_pad() otherwise webrtcbin will create an SDP offer with no - * media lines in it. */ + * get_request_pad() and before we go from NULL->READY otherwise webrtcbin + * will create an SDP offer with no media lines in it. */ if (offer) g_signal_connect (webrtc, "on-negotiation-needed", G_CALLBACK (on_negotiation_needed), (gpointer) peer_id); @@ -325,22 +372,23 @@ add_webrtcbin_to_pipeline (const gchar * peer_id, gboolean offer) g_signal_connect (webrtc, "pad-added", G_CALLBACK (on_incoming_stream), pipeline); - /* Set to bin to PLAYING */ + /* Set to pipeline branch to PLAYING */ + ret = gst_element_sync_state_with_parent (q); + g_assert_true (ret); ret = gst_element_sync_state_with_parent (webrtc); - gst_object_unref (webrtc); g_assert_true (ret); } static void call_peer (const gchar * peer_id) { - add_webrtcbin_to_pipeline (peer_id, TRUE); + add_peer_to_pipeline (peer_id, TRUE); } static void incoming_call_from_peer (const gchar * peer_id) { - add_webrtcbin_to_pipeline (peer_id, FALSE); + add_peer_to_pipeline (peer_id, FALSE); } #define STR(x) #x @@ -755,6 +803,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_assert_nonnull (peer_id); peers = g_list_remove (peers, peer_id); g_print ("Peer %s has left the room\n", peer_id); + remove_peer_from_pipeline (peer_id); g_free ((gchar*) peer_id); /* TODO: cleanup pipeline */ } else { From e5c5767298c0b1881267016bee9464865528ac9d Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Wed, 22 Nov 2017 13:15:48 +0100 Subject: [PATCH 210/412] Update to new promise API --- .../gst/mp-webrtc-sendrecv.c | 21 +++++++++++-------- webrtc/sendrecv/gst/webrtc-sendrecv.c | 11 +++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 184c02954e..5a257c6d63 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -252,11 +252,13 @@ on_offer_created (GstPromise * promise, const gchar * peer_id) { GstElement *webrtc; GstWebRTCSessionDescription *offer; + const GstStructure *reply; g_assert_cmpint (app_state, ==, ROOM_CALL_OFFERING); - g_assert_cmpint (promise->result, ==, GST_PROMISE_RESULT_REPLIED); - gst_structure_get (promise->promise, "offer", + g_assert_cmpint (gst_promise_wait (promise), ==, GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); gst_promise_unref (promise); @@ -275,12 +277,12 @@ on_offer_created (GstPromise * promise, const gchar * peer_id) static void on_negotiation_needed (GstElement * webrtc, const gchar * peer_id) { - GstPromise *promise = gst_promise_new (); + GstPromise *promise; app_state = ROOM_CALL_OFFERING; - g_signal_emit_by_name (webrtc, "create-offer", NULL, promise); - gst_promise_set_change_callback (promise, + promise = gst_promise_new_with_change_func ( (GstPromiseChangeFunc) on_offer_created, (gpointer) peer_id, NULL); + g_signal_emit_by_name (webrtc, "create-offer", NULL, promise); } static void @@ -576,11 +578,13 @@ on_answer_created (GstPromise * promise, const gchar * peer_id) { GstElement *webrtc; GstWebRTCSessionDescription *answer; + const GstStructure *reply; g_assert_cmpint (app_state, ==, ROOM_CALL_ANSWERING); - g_assert_cmpint (promise->result, ==, GST_PROMISE_RESULT_REPLIED); - gst_structure_get (promise->promise, "answer", + g_assert_cmpint (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); @@ -630,8 +634,7 @@ handle_sdp_offer (const gchar * peer_id, const gchar * text) gst_promise_unref (promise); /* Create an answer that we will send back to the peer */ - promise = gst_promise_new (); - gst_promise_set_change_callback (promise, + promise = gst_promise_new_with_change_func ( (GstPromiseChangeFunc) on_answer_created, (gpointer) peer_id, NULL); g_signal_emit_by_name (webrtc, "create-answer", NULL, promise); diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 3ee0e44789..bab104702e 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -219,12 +219,14 @@ static void on_offer_created (GstPromise * promise, gpointer user_data) { GstWebRTCSessionDescription *offer = NULL; + const GstStructure *reply; gchar *desc; g_assert (app_state == PEER_CALL_NEGOTIATING); - g_assert (promise->result == GST_PROMISE_RESULT_REPLIED); - gst_structure_get (promise->promise, "offer", + g_assert (gst_promise_wait(promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); gst_promise_unref (promise); @@ -241,12 +243,11 @@ on_offer_created (GstPromise * promise, gpointer user_data) static void on_negotiation_needed (GstElement * element, gpointer user_data) { - GstPromise *promise = gst_promise_new (); + 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); - gst_promise_set_change_callback (promise, on_offer_created, user_data, - NULL); } #define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=" From e4e83a648bd405e14bb87f2fdc26ae816e258892 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Thu, 23 Nov 2017 00:21:36 +1100 Subject: [PATCH 211/412] server/js: also allow running on localhost --- webrtc/sendrecv/js/webrtc.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index 31116c017a..b95df54dcd 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -137,7 +137,15 @@ function websocketServerConnect() { } peer_id = getOurId(); setStatus("Connecting to server"); - ws_conn = new WebSocket('wss://' + window.location.hostname + ':8443'); + loc = null; + if (window.location.protocol.startsWith ("file")) { + loc = "127.0.0.1"; + } else if (window.location.protocol.startsWith ("http")) { + loc = window.location.hostname; + } else { + throw new Error ("Don't know how to connect to the signalling server with uri" + window.location); + } + ws_conn = new WebSocket('wss://' + loc + ':8443'); /* When connected, immediately register with the server */ ws_conn.addEventListener('open', (event) => { document.getElementById("peer-id").textContent = peer_id; From 97cf7634208bbc87c491b299f5636ead7e965804 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 12 Dec 2017 21:40:09 +0530 Subject: [PATCH 212/412] sendrecv: Add a Google STUN server to the configuration Without this, the example will only work on link-local and localhost networks. --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index bab104702e..c823f651c9 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -250,6 +250,7 @@ on_negotiation_needed (GstElement * element, gpointer user_data) g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); } +#define STUN_SERVER " stun-server=stun://stun.l.google.com:19302 " #define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=" #define RTP_CAPS_VP8 "application/x-rtp,media=video,encoding-name=VP8,payload=" @@ -260,7 +261,7 @@ start_pipeline (void) GError *error = NULL; pipe1 = - gst_parse_launch ("webrtcbin name=sendrecv " + gst_parse_launch ("webrtcbin name=sendrecv " STUN_SERVER "videotestsrc pattern=ball ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " "audiotestsrc wave=red-noise ! queue ! opusenc ! rtpopuspay ! " From 48247c6ab6807d6ae179cf653cbdc64714d3313c Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Sun, 31 Dec 2017 13:21:30 +0000 Subject: [PATCH 213/412] meson: fix version number Current development version is 1.13.0.1. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a033267b9f..49878ece73 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,3 @@ -project('gst-examples', 'c', version : '1.11.1.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.13.0.1', license : 'LGPL') subdir('playback') subdir('network') From 43a27385c397d2bc6b6bf7a49fd774fa3ee200a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 2 Feb 2018 08:23:30 +0000 Subject: [PATCH 214/412] Update README Point to upstream repos now that it's been merged --- webrtc/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webrtc/README.md b/webrtc/README.md index 3c3ae09ef3..af1bc23912 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -2,11 +2,13 @@ All demos use the same signalling server in the `signalling/` directory -You will need the following repositories till the GStreamer WebRTC implementation is merged upstream: +The GStreamer WebRTC implementation has now been merged upstream, so all +you need is the latest GStreamer git master, as of 2 February 2018 or later. -https://github.com/ystreet/gstreamer/tree/promise - -https://github.com/ystreet/gst-plugins-bad/tree/webrtc +http://cgit.freedesktop.org/gstreamer/gstreamer +http://cgit.freedesktop.org/gstreamer/gst-plugins-base +http://cgit.freedesktop.org/gstreamer/gst-plugins-good +http://cgit.freedesktop.org/gstreamer/gst-plugins-bad You can build these with either Autotools gst-uninstalled: From 72c10e82436da3292530bfae1b3f8bc13af69c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 2 Feb 2018 08:39:04 +0000 Subject: [PATCH 215/412] webrtc-sendrecv: define GST_USE_UNSTABLE_API to avoid compiler warnings --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index c823f651c9..1de11c23ae 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -8,6 +8,8 @@ */ #include #include + +#define GST_USE_UNSTABLE_API #include /* For signalling */ From 2e5204ae3b30b7938d5821cf25ddfc50fa45f747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 2 Feb 2018 08:41:21 +0000 Subject: [PATCH 216/412] README: fix formatting --- webrtc/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webrtc/README.md b/webrtc/README.md index af1bc23912..2bbaa6be4f 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -3,12 +3,12 @@ All demos use the same signalling server in the `signalling/` directory The GStreamer WebRTC implementation has now been merged upstream, so all -you need is the latest GStreamer git master, as of 2 February 2018 or later. +you need is the latest GStreamer git master, as of 2 February 2018 or later: -http://cgit.freedesktop.org/gstreamer/gstreamer -http://cgit.freedesktop.org/gstreamer/gst-plugins-base -http://cgit.freedesktop.org/gstreamer/gst-plugins-good -http://cgit.freedesktop.org/gstreamer/gst-plugins-bad + - http://cgit.freedesktop.org/gstreamer/gstreamer + - http://cgit.freedesktop.org/gstreamer/gst-plugins-base + - http://cgit.freedesktop.org/gstreamer/gst-plugins-good + - http://cgit.freedesktop.org/gstreamer/gst-plugins-bad You can build these with either Autotools gst-uninstalled: From 492d13a7c97ecb8dd4107680b40f9e4ddfff256e Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 17 Feb 2018 08:10:59 +0530 Subject: [PATCH 217/412] README: link to blog post, document multiparty example Also add TODO stubs for MCU and SFU --- webrtc/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/webrtc/README.md b/webrtc/README.md index 2bbaa6be4f..5083518324 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -1,7 +1,9 @@ -## GStreamer WebRTC demos +# GStreamer WebRTC demos All demos use the same signalling server in the `signalling/` directory +## Build + The GStreamer WebRTC implementation has now been merged upstream, so all you need is the latest GStreamer git master, as of 2 February 2018 or later: @@ -18,11 +20,23 @@ Or with Meson gst-build: https://cgit.freedesktop.org/gstreamer/gst-build/ +You may need to install the following packages using your package manager: + +json-glib, libsoup, libnice, libnice-gstreamer1 (the gstreamer plugin for libnice) + +## Documentation + +Currently, the best way to understand the API is to read the examples. This post breaking down the API should help with that: + +http://blog.nirbheek.in/2018/02/gstreamer-webrtc.html + +## Examples + ### sendrecv: Send and receive audio and video * Serve the `js/` directory on the root of your website, or open https://webrtc.nirbheek.in - The JS code assumes the signalling server is on port 8443 of the same server serving the HTML -* Build and run the sources in the `gst/` directory on your machine +* Build the sources in the `gst/` directory on your machine ```console $ gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o webrtc-sendrecv @@ -31,3 +45,29 @@ $ gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstrea * Open the website in a browser and ensure that the status is "Registered with server, waiting for call", and note the `id` too. * Run `webrtc-sendrecv --peer-id=ID` with the `id` from the browser. You will see state changes and an SDP exchange. * You will see a bouncing ball + hear red noise in the browser, and your browser's webcam + mic in the gst app + +TODO: Port to Python and Rust. + +### multiparty-sendrecv: Multiparty audio conference with N peers + +* Build the sources in the `gst/` directory on your machine + +```console +$ gcc mp-webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o mp-webrtc-sendrecv +``` + +* Run `mp-webrtc-sendrecv --room-id=ID` with `ID` as a room name. The peer will connect to the signalling server and setup a conference room. +* Run this as many times as you like, each will spawn a peer that sends red noise and outputs the red noise it receives from other peers. + - To change what a peer sends, find the `audiotestsrc` element in the source and change the `wave` property. + - You can, of course, also replace `audiotestsrc` itself with `autoaudiosrc` (any platform) or `pulsesink` (on linux). +* TODO: implement JS to do the same, derived from the JS for the `sendrecv` example. + +### TODO: Selective Forwarding Unit (SFU) example + +* Server routes media between peers +* Participant sends 1 stream, receives n-1 streams + +### TODO: Multipoint Control Unit (MCU) example + +* Server mixes media from all participants +* Participant sends 1 stream, receives 1 stream From fa2adc717bb66d9f08ddc34c157a7b83d040e1d4 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 8 Mar 2018 20:10:55 +0530 Subject: [PATCH 218/412] Fix crash on Windows by delimiting option entries with NULL Also use more verbose forms of g_assert which print values on failure --- .../gst/mp-webrtc-sendrecv.c | 1 + webrtc/sendrecv/gst/webrtc-sendrecv.c | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 5a257c6d63..00bcae433a 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -54,6 +54,7 @@ static GOptionEntry entries[] = { "name", 0, 0, G_OPTION_ARG_STRING, &local_id, "Name we will send to the server", "ID" }, { "room-id", 0, 0, G_OPTION_ARG_STRING, &room_id, "Room name to join or create", "ID" }, { "server", 0, 0, G_OPTION_ARG_STRING, &server_url, "Signalling server to connect to", "URL" }, + { NULL } }; static gint diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 1de11c23ae..28f9660fba 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -50,6 +50,7 @@ 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" }, + { NULL }, }; static gboolean @@ -105,12 +106,14 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, GstElement *q, *conv, *sink; GstPadLinkReturn ret; + g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); + q = gst_element_factory_make ("queue", NULL); - g_assert (q); + g_assert_nonnull (q); conv = gst_element_factory_make (convert_name, NULL); - g_assert (conv); + g_assert_nonnull (conv); sink = gst_element_factory_make (sink_name, NULL); - g_assert (sink); + g_assert_nonnull (sink); gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); gst_element_sync_state_with_parent (q); gst_element_sync_state_with_parent (conv); @@ -120,7 +123,7 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, qpad = gst_element_get_static_pad (q, "sink"); ret = gst_pad_link (pad, qpad); - g_assert (ret == GST_PAD_LINK_OK); + g_assert_cmphex (ret, ==, GST_PAD_LINK_OK); } static void @@ -224,9 +227,9 @@ on_offer_created (GstPromise * promise, gpointer user_data) const GstStructure *reply; gchar *desc; - g_assert (app_state == PEER_CALL_NEGOTIATING); + g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - g_assert (gst_promise_wait(promise) == GST_PROMISE_RESULT_REPLIED); + g_assert_cmphex (gst_promise_wait(promise), ==, GST_PROMISE_RESULT_REPLIED); reply = gst_promise_get_reply (promise); gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); @@ -277,7 +280,7 @@ start_pipeline (void) } webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "sendrecv"); - g_assert (webrtc1 != NULL); + g_assert_nonnull (webrtc1); /* This is the gstwebrtc entry point where we create the offer and so on. It * will be called when the pipeline goes to PLAYING. */ @@ -455,9 +458,9 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, GstSDPMessage *sdp; GstWebRTCSessionDescription *answer; - g_assert (app_state == PEER_CALL_NEGOTIATING); + g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - g_assert (json_object_has_member (object, "type")); + g_assert_true (json_object_has_member (object, "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. */ @@ -469,14 +472,14 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_print ("Received answer:\n%s\n", text); ret = gst_sdp_message_new (&sdp); - g_assert (ret == GST_SDP_OK); + g_assert_cmphex (ret, ==, GST_SDP_OK); ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); - g_assert (ret == GST_SDP_OK); + g_assert_cmphex (ret, ==, GST_SDP_OK); answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, sdp); - g_assert (answer); + g_assert_nonnull (answer); /* Set remote description on our pipeline */ { @@ -523,7 +526,7 @@ on_server_connected (SoupSession * session, GAsyncResult * res, return; } - g_assert (ws_conn != NULL); + g_assert_nonnull (ws_conn); app_state = SERVER_CONNECTED; g_print ("Connected to signalling server\n"); From 55e86469d9aab4adf24dee86e3e4c06b623bcea1 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 10 Mar 2018 01:54:48 +0530 Subject: [PATCH 219/412] Check for all necessary plugins at startup People seem to be having problems ensuring that they have all the right plugins built, so make it a bit easier for them. --- .../gst/mp-webrtc-sendrecv.c | 27 +++++++++++++++++++ webrtc/sendrecv/gst/webrtc-sendrecv.c | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 00bcae433a..4a99eb8f3c 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -893,6 +893,30 @@ connect_to_websocket_server_async (void) app_state = SERVER_CONNECTING; } +static gboolean +check_plugins (void) +{ + int i; + gboolean ret; + GstPlugin *plugin; + GstRegistry *registry; + const gchar *needed[] = { "opus", "nice", "webrtc", "dtls", "srtp" + "rtpmanager", "audiotestsrc", NULL}; + + registry = gst_registry_get (); + ret = TRUE; + for (i = 0; i < g_strv_length ((gchar **) needed); i++) { + plugin = gst_registry_find_plugin (registry, needed[i]); + if (!plugin) { + g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); + ret = FALSE; + continue; + } + gst_object_unref (plugin); + } + return ret; +} + int main (int argc, char *argv[]) { @@ -908,6 +932,9 @@ main (int argc, char *argv[]) return -1; } + if (!check_plugins ()) + return -1; + if (!room_id) { g_printerr ("--room-id is a required argument\n"); return -1; diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 28f9660fba..fcad886ad4 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -568,6 +568,30 @@ connect_to_websocket_server_async (void) app_state = SERVER_CONNECTING; } +static gboolean +check_plugins (void) +{ + int i; + gboolean ret; + GstPlugin *plugin; + GstRegistry *registry; + const gchar *needed[] = { "opus", "vpx", "nice", "webrtc", "dtls", "srtp", + "rtpmanager", "videotestsrc", "audiotestsrc", NULL}; + + registry = gst_registry_get (); + ret = TRUE; + for (i = 0; i < g_strv_length ((gchar **) needed); i++) { + plugin = gst_registry_find_plugin (registry, needed[i]); + if (!plugin) { + g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); + ret = FALSE; + continue; + } + gst_object_unref (plugin); + } + return ret; +} + int main (int argc, char *argv[]) { @@ -583,6 +607,9 @@ main (int argc, char *argv[]) return -1; } + if (!check_plugins ()) + return -1; + if (!peer_id) { g_printerr ("--peer-id is a required argument\n"); return -1; From 429f4bb65f37819f12f313cb001adff0c3298027 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 10 Mar 2018 13:21:34 +0530 Subject: [PATCH 220/412] README.md: Document the binaries and Cerbero Also mention where to file bug reports about the plugin itself. --- webrtc/README.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/webrtc/README.md b/webrtc/README.md index 5083518324..5e8ac6769e 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -2,15 +2,29 @@ All demos use the same signalling server in the `signalling/` directory -## Build +## Downloading GStreamer -The GStreamer WebRTC implementation has now been merged upstream, so all -you need is the latest GStreamer git master, as of 2 February 2018 or later: +The GStreamer WebRTC implementation has now been merged upstream, and is in the GStreamer 1.14 release. Binaries can be found here: + +https://gstreamer.freedesktop.org/download/ + +## Building GStreamer from source + +If you don't want to use the binaries provided by GStreamer or on your Linux distro, you can build GStreamer from source. + +The easiest way to build the webrtc plugin and all the plugins it needs, is to [use Cerbero](https://gstreamer.freedesktop.org/documentation/installing/building-from-source-using-cerbero.html). These instructions should work out of the box for all platforms, including cross-compiling for iOS and Android. + +One thing to note is that it's written in Python 2, so you may need to replace all instances of `./cerbero-uninstalled` (or `cerbero`) with `python2 cerbero-uninstalled` or whatever Python 2 is called on your platform. + +## Building GStreamer manually from source + +For hacking on the webrtc plugin, you may want to build manually using the git repositories: - http://cgit.freedesktop.org/gstreamer/gstreamer - http://cgit.freedesktop.org/gstreamer/gst-plugins-base - http://cgit.freedesktop.org/gstreamer/gst-plugins-good - http://cgit.freedesktop.org/gstreamer/gst-plugins-bad + - http://cgit.freedesktop.org/libnice/libnice You can build these with either Autotools gst-uninstalled: @@ -24,6 +38,12 @@ You may need to install the following packages using your package manager: json-glib, libsoup, libnice, libnice-gstreamer1 (the gstreamer plugin for libnice) +## Filing bugs + +Please only file bugs about the demos here. Bugs about GStreamer's WebRTC implementation should be filed on the [GStreamer bugzilla](https://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer&component=gst-plugins-bad). + +You can also find us on IRC by joining #gstreamer @ FreeNode. + ## Documentation Currently, the best way to understand the API is to read the examples. This post breaking down the API should help with that: From 2b82525bb0a4afa2a9a82bffdd62f55a0c39f2c5 Mon Sep 17 00:00:00 2001 From: Sebastian Kilb Date: Wed, 21 Mar 2018 01:56:49 +0100 Subject: [PATCH 221/412] Fix audio/video linking error on windows Closes https://github.com/centricular/gstwebrtc-demos/issues/5 --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index fcad886ad4..268c898f0a 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -103,7 +103,7 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, const char * sink_name) { GstPad *qpad; - GstElement *q, *conv, *sink; + GstElement *q, *conv, *resample, *sink; GstPadLinkReturn ret; g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); @@ -114,11 +114,23 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, g_assert_nonnull (conv); sink = gst_element_factory_make (sink_name, NULL); g_assert_nonnull (sink); - gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); - gst_element_sync_state_with_parent (q); - gst_element_sync_state_with_parent (conv); - gst_element_sync_state_with_parent (sink); - gst_element_link_many (q, conv, sink, NULL); + + if (g_strcmp0 (convert_name, "audioconvert") == 0) { + resample = gst_element_factory_make ("audioresample", NULL); + g_assert_nonnull (resample); + gst_bin_add_many (GST_BIN (pipe), q, conv, resample, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (resample); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, resample, sink, NULL); + } else { + gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, sink, NULL); + } qpad = gst_element_get_static_pad (q, "sink"); From 20cf2503eee220e058a1b11e7a969dc9e85e3618 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 23 Mar 2018 11:36:40 +0530 Subject: [PATCH 222/412] sendrecv: Fix SDP message format The format is {'sdp': {'sdp': , 'type': }} The multiparty-sendrecv demo already uses this format. --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 26 ++++++++++++++++---------- webrtc/sendrecv/js/webrtc.js | 3 ++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 268c898f0a..ed0c8452cd 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -447,7 +447,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, /* Look for JSON messages containing SDP and ICE candidates */ } else { JsonNode *root; - JsonObject *object; + JsonObject *object, *child; JsonParser *parser = json_parser_new (); if (!json_parser_load_from_data (parser, text, -1, NULL)) { g_printerr ("Unknown message '%s', ignoring", text); @@ -466,20 +466,27 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, /* Check type of JSON message */ if (json_object_has_member (object, "sdp")) { int ret; - const gchar *text; GstSDPMessage *sdp; + const gchar *text, *sdptype; GstWebRTCSessionDescription *answer; g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - g_assert_true (json_object_has_member (object, "type")); + child = json_object_get_object_member (object, "sdp"); + + if (!json_object_has_member (child, "type")) { + cleanup_and_quit_loop ("ERROR: received SDP without 'type'", + PEER_CALL_ERROR); + goto out; + } + + 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 (json_object_get_string_member (object, "type"), ==, - "answer"); + g_assert_cmpstr (sdptype, ==, "answer"); - text = json_object_get_string_member (object, "sdp"); + text = json_object_get_string_member (child, "sdp"); g_print ("Received answer:\n%s\n", text); @@ -504,13 +511,12 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, app_state = PEER_CALL_STARTED; } else if (json_object_has_member (object, "ice")) { - JsonObject *ice; const gchar *candidate; gint sdpmlineindex; - ice = json_object_get_object_member (object, "ice"); - candidate = json_object_get_string_member (ice, "candidate"); - sdpmlineindex = json_object_get_int_member (ice, "sdpMLineIndex"); + child = json_object_get_object_member (object, "ice"); + candidate = json_object_get_string_member (child, "candidate"); + sdpmlineindex = json_object_get_int_member (child, "sdpMLineIndex"); /* Add ice candidate sent by remote peer */ g_signal_emit_by_name (webrtc1, "add-ice-candidate", sdpmlineindex, diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index b95df54dcd..bca5d7c798 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -63,7 +63,8 @@ function onLocalDescription(desc) { console.log("Got local description: " + JSON.stringify(desc)); peer_connection.setLocalDescription(desc).then(function() { setStatus("Sending SDP answer"); - ws_conn.send(JSON.stringify(peer_connection.localDescription)); + sdp = {'sdp': peer_connection.localDescription} + ws_conn.send(JSON.stringify(sdp)); }); } From 2d2bc0fe0e20bdaaf95fb42fed76d873696954c5 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 23 Mar 2018 12:05:09 +0530 Subject: [PATCH 223/412] Fix compiler warnings in all C demos --- webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c | 10 +++------- webrtc/sendrecv/gst/webrtc-sendrecv.c | 4 +--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 4a99eb8f3c..b0ab698bc2 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -8,6 +8,7 @@ */ #include #include +#define GST_USE_UNSTABLE_API #include /* For signalling */ @@ -327,7 +328,6 @@ add_peer_to_pipeline (const gchar * peer_id, gboolean offer) gchar *tmp; GstElement *tee, *webrtc, *q; GstPad *srcpad, *sinkpad; - GError *error = NULL; tmp = g_strdup_printf ("queue-%s", peer_id); q = gst_element_factory_make ("queue", tmp); @@ -619,7 +619,7 @@ handle_sdp_offer (const gchar * peer_id, const gchar * text) ret = gst_sdp_message_new (&sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); - ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); offer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_OFFER, sdp); @@ -659,7 +659,7 @@ handle_sdp_answer (const gchar * peer_id, const gchar * text) ret = gst_sdp_message_new (&sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); - ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, sdp); @@ -700,10 +700,7 @@ handle_peer_message (const gchar * peer_id, const gchar * msg) object = json_node_get_object (root); /* Check type of JSON message */ if (json_object_has_member (object, "sdp")) { - int ret; - GstSDPMessage *sdp; const gchar *text, *sdp_type; - GstWebRTCSessionDescription *answer; g_assert_cmpint (app_state, >=, ROOM_JOINED); @@ -920,7 +917,6 @@ check_plugins (void) int main (int argc, char *argv[]) { - SoupSession *session; GOptionContext *context; GError *error = NULL; diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index ed0c8452cd..f48e27a2d1 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -237,7 +237,6 @@ on_offer_created (GstPromise * promise, gpointer user_data) { GstWebRTCSessionDescription *offer = NULL; const GstStructure *reply; - gchar *desc; g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); @@ -493,7 +492,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, ret = gst_sdp_message_new (&sdp); g_assert_cmphex (ret, ==, GST_SDP_OK); - ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp); + 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, @@ -613,7 +612,6 @@ check_plugins (void) int main (int argc, char *argv[]) { - SoupSession *session; GOptionContext *context; GError *error = NULL; From 0e1be2a63f90e8bc73024dc333edcbd3eb4cb1e1 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 23 Mar 2018 12:10:26 +0530 Subject: [PATCH 224/412] Add Makefiles for all C demos --- webrtc/multiparty-sendrecv/gst/Makefile | 6 ++++++ webrtc/sendrecv/gst/Makefile | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 webrtc/multiparty-sendrecv/gst/Makefile create mode 100644 webrtc/sendrecv/gst/Makefile diff --git a/webrtc/multiparty-sendrecv/gst/Makefile b/webrtc/multiparty-sendrecv/gst/Makefile new file mode 100644 index 0000000000..ee1fef51e4 --- /dev/null +++ b/webrtc/multiparty-sendrecv/gst/Makefile @@ -0,0 +1,6 @@ +CC := gcc +LIBS := $(shell pkg-config --libs --cflags gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) +CFLAGS := -O0 -ggdb -Wall -fno-omit-frame-pointer + +mp-webrtc-sendrecv: mp-webrtc-sendrecv.c + "$(CC)" $(CFLAGS) $^ $(LIBS) -o $@ diff --git a/webrtc/sendrecv/gst/Makefile b/webrtc/sendrecv/gst/Makefile new file mode 100644 index 0000000000..9bce613729 --- /dev/null +++ b/webrtc/sendrecv/gst/Makefile @@ -0,0 +1,6 @@ +CC := gcc +LIBS := $(shell pkg-config --libs --cflags gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) +CFLAGS := -O0 -ggdb -Wall -fno-omit-frame-pointer + +webrtc-sendrecv: webrtc-sendrecv.c + "$(CC)" $(CFLAGS) $^ $(LIBS) -o $@ From 3f29b46283f1632581af1d056880f2e7aa98452d Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Thu, 29 Mar 2018 18:58:09 +0100 Subject: [PATCH 225/412] meson: Bump version to 1.15.0.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 49878ece73..33c97d28c5 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,3 @@ -project('gst-examples', 'c', version : '1.13.0.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.15.0.1', license : 'LGPL') subdir('playback') subdir('network') From 82314cabbb10fc338adf860e0276d067a76f2d92 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 31 Mar 2018 10:27:05 +0530 Subject: [PATCH 226/412] Don't use strict ssl certificate checking for localhost When using localhost signalling servers, we don't want to use strict ssl because it's probably using a self-signed certificate and there's no need to do certificate checking over localhost anyway. --- webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c | 13 ++++++++++++- webrtc/sendrecv/gst/webrtc-sendrecv.c | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index b0ab698bc2..413c83298d 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -49,6 +49,7 @@ static const gchar *default_server_url = "wss://webrtc.nirbheek.in:8443"; static gchar *server_url = NULL; static gchar *local_id = NULL; static gchar *room_id = NULL; +static gboolean strict_ssl = TRUE; static GOptionEntry entries[] = { @@ -871,7 +872,7 @@ connect_to_websocket_server_async (void) SoupSession *session; const char *https_aliases[] = {"wss", NULL}; - session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, TRUE, + session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, strict_ssl, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, //SOUP_SESSION_SSL_CA_FILE, "/etc/ssl/certs/ca-bundle.crt", SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); @@ -947,6 +948,16 @@ main (int argc, char *argv[]) if (!server_url) server_url = g_strdup (default_server_url); + /* Don't use strict ssl when running a localhost server, because + * it's probably a test server with a self-signed certificate */ + { + GstUri *uri = gst_uri_from_string (server_url); + if (g_strcmp0 ("localhost", gst_uri_get_host (uri)) == 0 || + g_strcmp0 ("127.0.0.1", gst_uri_get_host (uri)) == 0) + strict_ssl = FALSE; + gst_uri_unref (uri); + } + loop = g_main_loop_new (NULL, FALSE); connect_to_websocket_server_async (); diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index f48e27a2d1..607732e9d9 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -45,6 +45,7 @@ static SoupWebsocketConnection *ws_conn = NULL; static enum AppState app_state = 0; static const gchar *peer_id = NULL; static const gchar *server_url = "wss://webrtc.nirbheek.in:8443"; +static gboolean strict_ssl = TRUE; static GOptionEntry entries[] = { @@ -566,7 +567,7 @@ connect_to_websocket_server_async (void) SoupSession *session; const char *https_aliases[] = {"wss", NULL}; - session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, TRUE, + session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, strict_ssl, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, //SOUP_SESSION_SSL_CA_FILE, "/etc/ssl/certs/ca-bundle.crt", SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); @@ -631,6 +632,16 @@ main (int argc, char *argv[]) return -1; } + /* Don't use strict ssl when running a localhost server, because + * it's probably a test server with a self-signed certificate */ + { + GstUri *uri = gst_uri_from_string (server_url); + if (g_strcmp0 ("localhost", gst_uri_get_host (uri)) == 0 || + g_strcmp0 ("127.0.0.1", gst_uri_get_host (uri)) == 0) + strict_ssl = FALSE; + gst_uri_unref (uri); + } + loop = g_main_loop_new (NULL, FALSE); connect_to_websocket_server_async (); From 563826deaf63a156f41bfb0e7dc3fd4a0476d02a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 31 Mar 2018 10:28:51 +0530 Subject: [PATCH 227/412] sendrecv: Don't set pipeline state if it's NULL Avoids ugly CRITICAL warnings when erroring out. --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 607732e9d9..6ccefaa1f5 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -647,11 +647,13 @@ main (int argc, char *argv[]) connect_to_websocket_server_async (); g_main_loop_run (loop); + g_main_loop_unref (loop); - gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); - g_print ("Pipeline stopped\n"); - - gst_object_unref (pipe1); + if (pipe1) { + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + gst_object_unref (pipe1); + } return 0; } From bd6deaca46e48a0a7726031f474d1f71e8bf6723 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 31 Mar 2018 13:52:02 +0530 Subject: [PATCH 228/412] sendrecv/js: Call getUserMedia on incoming call Instead of registering it on page load. This will allow us to add an option for users to override the default constraints later. This is also generally nicer because the browser won't open the webcam immediately when you load the page and keep recording from it. --- webrtc/sendrecv/js/webrtc.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index bca5d7c798..865fcac6d2 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -13,6 +13,7 @@ var peer_connection = null; var rtc_configuration = {iceServers: [{urls: "stun:stun.services.mozilla.com"}, {urls: "stun:stun.l.google.com:19302"}]}; var ws_conn; +// Promise for local stream after constraints are approved by the user var local_stream; var peer_id; @@ -54,7 +55,10 @@ function onIncomingSDP(sdp) { if (sdp.type != "offer") return; setStatus("Got SDP offer, creating answer"); - peer_connection.createAnswer().then(onLocalDescription).catch(setStatus); + local_stream.then((stream) => { + peer_connection.createAnswer() + .then(onLocalDescription).catch(setStatus); + }).catch(errorUserMediaHandler); }).catch(setStatus); } @@ -130,6 +134,17 @@ function onServerError(event) { window.setTimeout(websocketServerConnect, 3000); } +function getLocalStream() { + var constraints = {video: true, audio: true}; + + // Add local stream + if (navigator.mediaDevices.getUserMedia) { + return navigator.mediaDevices.getUserMedia(constraints); + } else { + errorUserMediaHandler(); + } +} + function websocketServerConnect() { connect_attempts++; if (connect_attempts > 3) { @@ -156,17 +171,6 @@ function websocketServerConnect() { ws_conn.addEventListener('error', onServerError); ws_conn.addEventListener('message', onServerMessage); ws_conn.addEventListener('close', onServerClose); - - var constraints = {video: true, audio: true}; - - // Add local stream - if (navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia(constraints) - .then((stream) => { local_stream = stream }) - .catch(errorUserMediaHandler); - } else { - errorUserMediaHandler(); - } } function onRemoteStreamAdded(event) { @@ -192,7 +196,10 @@ function createCall(msg) { peer_connection = new RTCPeerConnection(rtc_configuration); peer_connection.onaddstream = onRemoteStreamAdded; /* Send our video/audio to the other peer */ - peer_connection.addStream(local_stream); + local_stream = getLocalStream().then((stream) => { + console.log('Adding local stream'); + peer_connection.addStream(stream); + }).catch(errorUserMediaHandler); if (!msg.sdp) { console.log("WARNING: First message wasn't an SDP message!?"); From 3eabe5cb0b6bd99cba3563444b2fa1fb14f6cb9e Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 31 Mar 2018 22:24:15 +0530 Subject: [PATCH 229/412] sendrecv/js: Make error statuses more prominent Colour errors in red, and ensure that later status updates don't overwrite existing error statuses. --- webrtc/sendrecv/js/index.html | 3 +++ webrtc/sendrecv/js/webrtc.js | 35 ++++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html index 0a0b7fec8a..1744715265 100644 --- a/webrtc/sendrecv/js/index.html +++ b/webrtc/sendrecv/js/index.html @@ -11,6 +11,9 @@ --> + \n \ + \n \ + \n \ + \n \ + \n \ +
\n \ + \n \ +
\n \ + \n \ + \n \ +"; + + + + +ReceiverEntry * +create_receiver_entry (SoupWebsocketConnection * connection) +{ + GError *error; + ReceiverEntry *receiver_entry; + GstWebRTCRTPTransceiver *trans; + GArray *transceivers; + + receiver_entry = g_slice_alloc0 (sizeof (ReceiverEntry)); + receiver_entry->connection = connection; + + g_object_ref (G_OBJECT (connection)); + + g_signal_connect (G_OBJECT (connection), "message", + G_CALLBACK (soup_websocket_message_cb), (gpointer) receiver_entry); + + error = NULL; + receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " + "v4l2src ! videorate ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " + "rtph264pay config-interval=-1 name=payloader ! " + "application/x-rtp,media=video,encoding-name=H264,payload=" + RTP_PAYLOAD_TYPE " ! webrtcbin. ", &error); + if (error != NULL) { + g_error ("Could not create WebRTC pipeline: %s\n", error->message); + g_error_free (error); + goto cleanup; + } + + receiver_entry->webrtcbin = + gst_bin_get_by_name (GST_BIN (receiver_entry->pipeline), "webrtcbin"); + g_assert (receiver_entry->webrtcbin != NULL); + + g_signal_emit_by_name (receiver_entry->webrtcbin, "get-transceivers", &transceivers); + g_assert (transceivers != NULL && transceivers->len > 0); + trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); + trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + + g_signal_connect (receiver_entry->webrtcbin, "on-negotiation-needed", + G_CALLBACK (on_negotiation_needed_cb), (gpointer) receiver_entry); + + g_signal_connect (receiver_entry->webrtcbin, "on-ice-candidate", + G_CALLBACK (on_ice_candidate_cb), (gpointer) receiver_entry); + + gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING); + + return receiver_entry; + +cleanup: + destroy_receiver_entry ((gpointer) receiver_entry); + return NULL; +} + +void +destroy_receiver_entry (gpointer receiver_entry_ptr) +{ + ReceiverEntry *receiver_entry = (ReceiverEntry *) receiver_entry_ptr; + + g_assert (receiver_entry != NULL); + + if (receiver_entry->pipeline != NULL) { + gst_element_set_state (GST_ELEMENT (receiver_entry->pipeline), + GST_STATE_NULL); + + gst_object_unref (GST_OBJECT (receiver_entry->webrtcbin)); + gst_object_unref (GST_OBJECT (receiver_entry->pipeline)); + } + + if (receiver_entry->connection != NULL) + g_object_unref (G_OBJECT (receiver_entry->connection)); + + g_slice_free1 (sizeof (ReceiverEntry), receiver_entry); +} + + +void +on_offer_created_cb (GstPromise * promise, gpointer user_data) +{ + gchar *sdp_string; + gchar *json_string; + JsonObject *sdp_json; + JsonObject *sdp_data_json; + GstStructure const *reply; + GstPromise *local_desc_promise; + GstWebRTCSessionDescription *offer = NULL; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, + &offer, NULL); + gst_promise_unref (promise); + + local_desc_promise = gst_promise_new (); + g_signal_emit_by_name (receiver_entry->webrtcbin, "set-local-description", + offer, local_desc_promise); + gst_promise_interrupt (local_desc_promise); + gst_promise_unref (local_desc_promise); + + sdp_string = gst_sdp_message_as_text (offer->sdp); + g_print ("Negotiation offer created:\n%s\n", sdp_string); + + sdp_json = json_object_new (); + json_object_set_string_member (sdp_json, "type", "sdp"); + + sdp_data_json = json_object_new (); + json_object_set_string_member (sdp_data_json, "type", "offer"); + json_object_set_string_member (sdp_data_json, "sdp", sdp_string); + json_object_set_object_member (sdp_json, "data", sdp_data_json); + + json_string = get_string_from_json_object (sdp_json); + json_object_unref (sdp_json); + + soup_websocket_connection_send_text (receiver_entry->connection, json_string); + g_free (json_string); + g_free (sdp_string); + + gst_webrtc_session_description_free (offer); +} + + +void +on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data) +{ + GstPromise *promise; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + g_print ("Creating negotiation offer\n"); + + promise = gst_promise_new_with_change_func (on_offer_created_cb, + (gpointer) receiver_entry, NULL); + g_signal_emit_by_name (G_OBJECT (webrtcbin), "create-offer", NULL, promise); +} + + +void +on_ice_candidate_cb (G_GNUC_UNUSED GstElement * webrtcbin, guint mline_index, + gchar * candidate, gpointer user_data) +{ + JsonObject *ice_json; + JsonObject *ice_data_json; + gchar *json_string; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + ice_json = json_object_new (); + json_object_set_string_member (ice_json, "type", "ice"); + + ice_data_json = json_object_new (); + json_object_set_int_member (ice_data_json, "sdpMLineIndex", mline_index); + json_object_set_string_member (ice_data_json, "candidate", candidate); + json_object_set_object_member (ice_json, "data", ice_data_json); + + json_string = get_string_from_json_object (ice_json); + json_object_unref (ice_json); + + soup_websocket_connection_send_text (receiver_entry->connection, json_string); + g_free (json_string); +} + + +void +soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, + SoupWebsocketDataType data_type, GBytes * message, gpointer user_data) +{ + gsize size; + gchar *data; + gchar *data_string; + const gchar *type_string; + JsonNode *root_json; + JsonObject *root_json_object; + JsonObject *data_json_object; + JsonParser *json_parser = NULL; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + switch (data_type) { + case SOUP_WEBSOCKET_DATA_BINARY: + g_error ("Received unknown binary message, ignoring\n"); + g_bytes_unref (message); + return; + + case SOUP_WEBSOCKET_DATA_TEXT: + data = g_bytes_unref_to_data (message, &size); + /* Convert to NULL-terminated string */ + data_string = g_strndup (data, size); + g_free (data); + break; + + default: + g_assert_not_reached (); + } + + json_parser = json_parser_new (); + if (!json_parser_load_from_data (json_parser, data_string, -1, NULL)) + goto unknown_message; + + root_json = json_parser_get_root (json_parser); + if (!JSON_NODE_HOLDS_OBJECT (root_json)) + goto unknown_message; + + root_json_object = json_node_get_object (root_json); + + if (!json_object_has_member (root_json_object, "type")) { + g_error ("Received message without type field\n"); + goto cleanup; + } + type_string = json_object_get_string_member (root_json_object, "type"); + + if (!json_object_has_member (root_json_object, "data")) { + g_error ("Received message without data field\n"); + goto cleanup; + } + data_json_object = json_object_get_object_member (root_json_object, "data"); + + if (g_strcmp0 (type_string, "sdp") == 0) { + const gchar *sdp_type_string; + const gchar *sdp_string; + GstPromise *promise; + GstSDPMessage *sdp; + GstWebRTCSessionDescription *answer; + int ret; + + if (!json_object_has_member (data_json_object, "type")) { + g_error ("Received SDP message without type field\n"); + goto cleanup; + } + sdp_type_string = json_object_get_string_member (data_json_object, "type"); + + if (g_strcmp0 (sdp_type_string, "answer") != 0) { + g_error ("Expected SDP message type \"answer\", got \"%s\"\n", + sdp_type_string); + goto cleanup; + } + + if (!json_object_has_member (data_json_object, "sdp")) { + g_error ("Received SDP message without SDP string\n"); + goto cleanup; + } + sdp_string = json_object_get_string_member (data_json_object, "sdp"); + + g_print ("Received SDP:\n%s\n", sdp_string); + + ret = gst_sdp_message_new (&sdp); + g_assert_cmphex (ret, ==, GST_SDP_OK); + + ret = + gst_sdp_message_parse_buffer ((guint8 *) sdp_string, + strlen (sdp_string), sdp); + if (ret != GST_SDP_OK) { + g_error ("Could not parse SDP string\n"); + goto cleanup; + } + + answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, + sdp); + g_assert_nonnull (answer); + + promise = gst_promise_new (); + g_signal_emit_by_name (receiver_entry->webrtcbin, "set-remote-description", + answer, promise); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + gst_webrtc_session_description_free (answer); + } else if (g_strcmp0 (type_string, "ice") == 0) { + guint mline_index; + const gchar *candidate_string; + + if (!json_object_has_member (data_json_object, "sdpMLineIndex")) { + g_error ("Received ICE message without mline index\n"); + goto cleanup; + } + mline_index = + json_object_get_int_member (data_json_object, "sdpMLineIndex"); + + if (!json_object_has_member (data_json_object, "candidate")) { + g_error ("Received ICE message without ICE candidate string\n"); + goto cleanup; + } + candidate_string = json_object_get_string_member (data_json_object, + "candidate"); + + g_print ("Received ICE candidate with mline index %u; candidate: %s\n", + mline_index, candidate_string); + + g_signal_emit_by_name (receiver_entry->webrtcbin, "add-ice-candidate", + mline_index, candidate_string); + } else + goto unknown_message; + +cleanup: + if (json_parser != NULL) + g_object_unref (G_OBJECT (json_parser)); + g_free (data_string); + return; + +unknown_message: + g_error ("Unknown message \"%s\", ignoring", data_string); + goto cleanup; +} + + +void +soup_websocket_closed_cb (SoupWebsocketConnection * connection, + gpointer user_data) +{ + GHashTable *receiver_entry_table = (GHashTable *) user_data; + g_hash_table_remove (receiver_entry_table, connection); + g_print ("Closed websocket connection %p\n", (gpointer) connection); +} + + +void +soup_http_handler (G_GNUC_UNUSED SoupServer * soup_server, + SoupMessage * message, const char *path, G_GNUC_UNUSED GHashTable * query, + G_GNUC_UNUSED SoupClientContext * client_context, + G_GNUC_UNUSED gpointer user_data) +{ + SoupBuffer *soup_buffer; + + if ((g_strcmp0 (path, "/") != 0) && (g_strcmp0 (path, "/index.html") != 0)) { + soup_message_set_status (message, SOUP_STATUS_NOT_FOUND); + return; + } + + soup_buffer = + soup_buffer_new (SOUP_MEMORY_STATIC, html_source, strlen (html_source)); + + soup_message_headers_set_content_type (message->response_headers, "text/html", + NULL); + soup_message_body_append_buffer (message->response_body, soup_buffer); + soup_buffer_free (soup_buffer); + + soup_message_set_status (message, SOUP_STATUS_OK); +} + + +void +soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, + SoupWebsocketConnection * connection, G_GNUC_UNUSED const char *path, + G_GNUC_UNUSED SoupClientContext * client_context, gpointer user_data) +{ + ReceiverEntry *receiver_entry; + GHashTable *receiver_entry_table = (GHashTable *) user_data; + + g_print ("Processing new websocket connection %p", (gpointer) connection); + + g_signal_connect (G_OBJECT (connection), "closed", + G_CALLBACK (soup_websocket_closed_cb), (gpointer) receiver_entry_table); + + receiver_entry = create_receiver_entry (connection); + g_hash_table_replace (receiver_entry_table, connection, receiver_entry); +} + + +static gchar * +get_string_from_json_object (JsonObject * object) +{ + JsonNode *root; + JsonGenerator *generator; + gchar *text; + + /* Make it the root node */ + root = json_node_init_object (json_node_alloc (), object); + generator = json_generator_new (); + json_generator_set_root (generator, root); + text = json_generator_to_data (generator, NULL); + + /* Release everything */ + g_object_unref (generator); + json_node_free (root); + return text; +} + + +gboolean +exit_sighandler (gpointer user_data) +{ + g_print ("Caught signal, stopping mainloop\n"); + GMainLoop *mainloop = (GMainLoop *) user_data; + g_main_loop_quit (mainloop); + return TRUE; +} + + +int +main (int argc, char *argv[]) +{ + GMainLoop *mainloop; + SoupServer *soup_server; + GHashTable *receiver_entry_table; + + setlocale (LC_ALL, ""); + gst_init (&argc, &argv); + + receiver_entry_table = + g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + destroy_receiver_entry); + + mainloop = g_main_loop_new (NULL, FALSE); + g_assert (mainloop != NULL); + + g_unix_signal_add (SIGINT, exit_sighandler, mainloop); + g_unix_signal_add (SIGTERM, exit_sighandler, mainloop); + + soup_server = + soup_server_new (SOUP_SERVER_SERVER_HEADER, "webrtc-soup-server", NULL); + soup_server_add_handler (soup_server, "/", soup_http_handler, NULL, NULL); + soup_server_add_websocket_handler (soup_server, "/ws", NULL, NULL, + soup_websocket_handler, (gpointer) receiver_entry_table, NULL); + soup_server_listen_all (soup_server, SOUP_HTTP_PORT, + (SoupServerListenOptions) 0, NULL); + + g_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); + + g_main_loop_run (mainloop); + + g_object_unref (G_OBJECT (soup_server)); + g_hash_table_destroy (receiver_entry_table); + g_main_loop_unref (mainloop); + + gst_deinit (); + + return 0; +} From 3cabee61c79c4f8321cf718770fff5e06a0b7c91 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Wed, 15 Jan 2020 10:47:27 +1100 Subject: [PATCH 323/412] Add python Janus videoroom streaming example. Added with permission and copyright @tobiasfriden and @saket424 on github. See https://github.com/centricular/gstwebrtc-demos/issues/66 --- webrtc/janus/janusvideoroom.py | 443 +++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 webrtc/janus/janusvideoroom.py diff --git a/webrtc/janus/janusvideoroom.py b/webrtc/janus/janusvideoroom.py new file mode 100644 index 0000000000..80ee47fef5 --- /dev/null +++ b/webrtc/janus/janusvideoroom.py @@ -0,0 +1,443 @@ +# Janus Videoroom example +# Copyright @tobiasfriden and @saket424 on github +# See https://github.com/centricular/gstwebrtc-demos/issues/66 +# Copyright Jan Schmidt 2020 +import random +import ssl +import websockets +import asyncio +import os +import sys +import json +import argparse +import string +from websockets.exceptions import ConnectionClosed + +import attr + +@attr.s +class JanusEvent: + sender = attr.ib(validator=attr.validators.instance_of(int)) + +@attr.s +class PluginData(JanusEvent): + plugin = attr.ib(validator=attr.validators.instance_of(str)) + data = attr.ib() + jsep = attr.ib() + +@attr.s +class WebrtcUp(JanusEvent): + pass + +@attr.s +class Media(JanusEvent): + receiving = attr.ib(validator=attr.validators.instance_of(bool)) + kind = attr.ib(validator=attr.validators.in_(["audio", "video"])) + + @kind.validator + def validate_kind(self, attribute, kind): + if kind not in ["video", "audio"]: + raise ValueError("kind must equal video or audio") + +@attr.s +class SlowLink(JanusEvent): + uplink = attr.ib(validator=attr.validators.instance_of(bool)) + lost = attr.ib(validator=attr.validators.instance_of(int)) + +@attr.s +class HangUp(JanusEvent): + reason = attr.ib(validator=attr.validators.instance_of(str)) + +@attr.s(cmp=False) +class Ack: + transaction = attr.ib(validator=attr.validators.instance_of(str)) + +@attr.s +class Jsep: + sdp = attr.ib() + type = attr.ib(validator=attr.validators.in_(["offer", "pranswer", "answer", "rollback"])) + + +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst +gi.require_version('GstWebRTC', '1.0') +from gi.repository import GstWebRTC +gi.require_version('GstSdp', '1.0') +from gi.repository import GstSdp + +DO_VP8 = True + +if DO_VP8: + ( encoder, payloader, rtp_encoding) = ( "vp8enc target-bitrate=500000", "rtpvp8pay", "VP8" ) +else: + ( encoder, payloader, rtp_encoding) = ( "x264enc", "rtph264pay", "H264" ) + +PIPELINE_DESC = ''' + webrtcbin name=sendrecv stun-server=stun://stun.l.google.com:19302 + videotestsrc pattern=ball ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! + {} ! {} ! queue ! application/x-rtp,media=video,encoding-name={},payload=96 ! sendrecv. +'''.format(encoder, payloader, rtp_encoding) + +def transaction_id(): + return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) + +@attr.s +class JanusGateway: + server = attr.ib(validator=attr.validators.instance_of(str)) + #secure = attr.ib(default=True) + _messages = attr.ib(factory=set) + conn = None + + async def connect(self): + sslCon=None + if self.server.startswith("wss"): + sslCon=ssl.SSLContext() + self.conn = await websockets.connect(self.server, subprotocols=['janus-protocol'], ssl=sslCon) + transaction = transaction_id() + await self.conn.send(json.dumps({ + "janus": "create", + "transaction": transaction + })) + resp = await self.conn.recv() + print (resp) + parsed = json.loads(resp) + assert parsed["janus"] == "success", "Failed creating session" + assert parsed["transaction"] == transaction, "Incorrect transaction" + self.session = parsed["data"]["id"] + + async def close(self): + if self.conn: + await self.conn.close() + + async def attach(self, plugin): + assert hasattr(self, "session"), "Must connect before attaching to plugin" + transaction = transaction_id() + await self.conn.send(json.dumps({ + "janus": "attach", + "session_id": self.session, + "plugin": plugin, + "transaction": transaction + })) + resp = await self.conn.recv() + parsed = json.loads(resp) + assert parsed["janus"] == "success", "Failed attaching to {}".format(plugin) + assert parsed["transaction"] == transaction, "Incorrect transaction" + self.handle = parsed["data"]["id"] + + async def sendtrickle(self, candidate): + assert hasattr(self, "session"), "Must connect before sending messages" + assert hasattr(self, "handle"), "Must attach before sending messages" + + transaction = transaction_id() + janus_message = { + "janus": "trickle", + "session_id": self.session, + "handle_id": self.handle, + "transaction": transaction, + "candidate": candidate + } + + await self.conn.send(json.dumps(janus_message)) + + #while True: + # resp = await self._recv_and_parse() + # if isinstance(resp, PluginData): + # return resp + # else: + # self._messages.add(resp) +# + async def sendmessage(self, body, jsep=None): + assert hasattr(self, "session"), "Must connect before sending messages" + assert hasattr(self, "handle"), "Must attach before sending messages" + + transaction = transaction_id() + janus_message = { + "janus": "message", + "session_id": self.session, + "handle_id": self.handle, + "transaction": transaction, + "body": body + } + if jsep is not None: + janus_message["jsep"] = jsep + + await self.conn.send(json.dumps(janus_message)) + + #while True: + # resp = await self._recv_and_parse() + # if isinstance(resp, PluginData): + # if jsep is not None: + # await client.handle_sdp(resp.jsep) + # return resp + # else: + # self._messages.add(resp) + + async def keepalive(self): + assert hasattr(self, "session"), "Must connect before sending messages" + assert hasattr(self, "handle"), "Must attach before sending messages" + + while True: + try: + await asyncio.sleep(10) + transaction = transaction_id() + await self.conn.send(json.dumps({ + "janus": "keepalive", + "session_id": self.session, + "handle_id": self.handle, + "transaction": transaction + })) + except KeyboardInterrupt: + return + + async def recv(self): + if len(self._messages) > 0: + return self._messages.pop() + else: + return await self._recv_and_parse() + + async def _recv_and_parse(self): + raw = json.loads(await self.conn.recv()) + janus = raw["janus"] + + if janus == "event": + return PluginData( + sender=raw["sender"], + plugin=raw["plugindata"]["plugin"], + data=raw["plugindata"]["data"], + jsep=raw["jsep"] if "jsep" in raw else None + ) + elif janus == "webrtcup": + return WebrtcUp( + sender=raw["sender"] + ) + elif janus == "media": + return Media( + sender=raw["sender"], + receiving=raw["receiving"], + kind=raw["type"] + ) + elif janus == "slowlink": + return SlowLink( + sender=raw["sender"], + uplink=raw["uplink"], + lost=raw["lost"] + ) + elif janus == "hangup": + return HangUp( + sender=raw["sender"], + reason=raw["reason"] + ) + elif janus == "ack": + return Ack( + transaction=raw["transaction"] + ) + else: + return raw + +class WebRTCClient: + def __init__(self, id_, peer_id, server, signaling): + self.id_ = id_ + self.conn = None + self.pipe = None + self.webrtc = None + self.peer_id = peer_id + self.server = server or 'wss://127.0.0.1:8989' + self.signaling = signaling + self.request = None + self.offermsg = None + + def send_sdp_offer(self, offer): + text = offer.sdp.as_text() + print ('Sending offer:\n%s' % text) + # configure media + media = {'audio': True, 'video': True} + request = {'request': 'publish'} + request.update(media) + self.request = request + self.offermsg = { 'sdp': text, 'trickle': True, 'type': 'offer' } + print (self.offermsg) + loop = asyncio.new_event_loop() + loop.run_until_complete(self.signaling.sendmessage(self.request, self.offermsg)) + + def on_offer_created(self, promise, _, __): + promise.wait() + reply = promise.get_reply() + offer = reply.get_value('offer') + promise = Gst.Promise.new() + self.webrtc.emit('set-local-description', offer, promise) + promise.interrupt() + self.send_sdp_offer(offer) + + def on_negotiation_needed(self, element): + promise = Gst.Promise.new_with_change_func(self.on_offer_created, element, None) + element.emit('create-offer', None, promise) + + def send_ice_candidate_message(self, _, mlineindex, candidate): + icemsg = {'candidate': candidate, 'sdpMLineIndex': mlineindex} + print ("Sending ICE", icemsg) + loop = asyncio.new_event_loop() + loop.run_until_complete(self.signaling.sendtrickle(icemsg)) + + def on_incoming_decodebin_stream(self, _, pad): + if not pad.has_current_caps(): + print (pad, 'has no caps, ignoring') + return + + caps = pad.get_current_caps() + name = caps.to_string() + if name.startswith('video'): + q = Gst.ElementFactory.make('queue') + conv = Gst.ElementFactory.make('videoconvert') + sink = Gst.ElementFactory.make('autovideosink') + self.pipe.add(q) + self.pipe.add(conv) + self.pipe.add(sink) + self.pipe.sync_children_states() + pad.link(q.get_static_pad('sink')) + q.link(conv) + conv.link(sink) + elif name.startswith('audio'): + q = Gst.ElementFactory.make('queue') + conv = Gst.ElementFactory.make('audioconvert') + resample = Gst.ElementFactory.make('audioresample') + sink = Gst.ElementFactory.make('autoaudiosink') + self.pipe.add(q) + self.pipe.add(conv) + self.pipe.add(resample) + self.pipe.add(sink) + self.pipe.sync_children_states() + pad.link(q.get_static_pad('sink')) + q.link(conv) + conv.link(resample) + resample.link(sink) + + def on_incoming_stream(self, _, pad): + if pad.direction != Gst.PadDirection.SRC: + return + + decodebin = Gst.ElementFactory.make('decodebin') + decodebin.connect('pad-added', self.on_incoming_decodebin_stream) + self.pipe.add(decodebin) + decodebin.sync_state_with_parent() + self.webrtc.link(decodebin) + + def start_pipeline(self): + self.pipe = Gst.parse_launch(PIPELINE_DESC) + self.webrtc = self.pipe.get_by_name('sendrecv') + self.webrtc.connect('on-negotiation-needed', self.on_negotiation_needed) + self.webrtc.connect('on-ice-candidate', self.send_ice_candidate_message) + self.webrtc.connect('pad-added', self.on_incoming_stream) + self.pipe.set_state(Gst.State.PLAYING) + + def extract_ice_from_sdp(self, sdp): + mlineindex = -1 + for line in sdp.splitlines(): + if line.startswith("a=candidate"): + candidate = line[2:] + if mlineindex < 0: + print("Received ice candidate in SDP before any m= line") + continue + print ('Received remote ice-candidate mlineindex {}: {}'.format(mlineindex, candidate)) + self.webrtc.emit('add-ice-candidate', mlineindex, candidate) + elif line.startswith("m="): + mlineindex += 1 + + async def handle_sdp(self, msg): + print (msg) + if 'sdp' in msg: + sdp = msg['sdp'] + assert(msg['type'] == 'answer') + print ('Received answer:\n%s' % sdp) + res, sdpmsg = GstSdp.SDPMessage.new() + GstSdp.sdp_message_parse_buffer(bytes(sdp.encode()), sdpmsg) + + answer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.ANSWER, sdpmsg) + promise = Gst.Promise.new() + self.webrtc.emit('set-remote-description', answer, promise) + promise.interrupt() + + # Extract ICE candidates from the SDP to work around a GStreamer + # limitation in (at least) 1.16.2 and below + self.extract_ice_from_sdp (sdp) + + elif 'ice' in msg: + ice = msg['ice'] + candidate = ice['candidate'] + sdpmlineindex = ice['sdpMLineIndex'] + self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) + + async def loop(self, signaling): + await signaling.connect() + await signaling.attach("janus.plugin.videoroom") + + loop = asyncio.get_event_loop() + loop.create_task(signaling.keepalive()) + #asyncio.create_task(self.keepalive()) + + joinmessage = { "request": "join", "ptype": "publisher", "room": 1234, "display": self.peer_id } + await signaling.sendmessage(joinmessage) + + assert signaling.conn + self.start_pipeline() + + while True: + try: + msg = await signaling.recv() + if isinstance(msg, PluginData): + if msg.jsep is not None: + await self.handle_sdp(msg.jsep) + elif isinstance(msg, Media): + print (msg) + elif isinstance(msg, WebrtcUp): + print (msg) + elif isinstance(msg, SlowLink): + print (msg) + elif isinstance(msg, HangUp): + print (msg) + elif not isinstance(msg, Ack): + if 'candidate' in msg: + ice = msg['candidate'] + print (ice) + if 'candidate' in ice: + candidate = ice['candidate'] + sdpmlineindex = ice['sdpMLineIndex'] + self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) + print(msg) + except (KeyboardInterrupt, ConnectionClosed): + return + + return 0 + + +def check_plugins(): + needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", + "rtpmanager", "videotestsrc", "audiotestsrc"] + missing = list(filter(lambda p: Gst.Registry.get().find_plugin(p) is None, needed)) + if len(missing): + print('Missing gstreamer plugins:', missing) + return False + return True + + +if __name__=='__main__': + Gst.init(None) + if not check_plugins(): + sys.exit(1) + parser = argparse.ArgumentParser() + parser.add_argument('label', help='videoroom label') + parser.add_argument('--server', help='Signalling server to connect to, eg "wss://127.0.0.1:8989"') + args = parser.parse_args() + our_id = random.randrange(10, 10000) + signaling = JanusGateway(args.server) + c = WebRTCClient(our_id, args.label, args.server, signaling) + loop = asyncio.get_event_loop() + try: + loop.run_until_complete( + c.loop(signaling) + ) + except KeyboardInterrupt: + pass + finally: + print("Interrupted, cleaning up") + loop.run_until_complete(signaling.close()) From 42c6eac7f1b3227c9a3a91851840fd7270bce4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 23 Jan 2020 08:35:25 +0200 Subject: [PATCH 324/412] Update dependencies of Rust examples and simplify slightly --- .../multiparty-sendrecv/gst-rust/Cargo.lock | 250 +++++++++--------- .../multiparty-sendrecv/gst-rust/src/main.rs | 10 +- webrtc/sendrecv/gst-rust/Cargo.lock | 137 +++++----- webrtc/sendrecv/gst-rust/src/main.rs | 10 +- 4 files changed, 208 insertions(+), 199 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock index 053bacdf35..fb1183fdec 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock @@ -7,7 +7,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "async-native-tls" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -20,7 +20,7 @@ name = "async-std" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-task 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -29,34 +29,35 @@ dependencies = [ "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-task" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-tungstenite" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-native-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -65,6 +66,11 @@ name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base64" version = "0.11.0" @@ -123,7 +129,7 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -289,9 +295,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -320,7 +326,7 @@ dependencies = [ "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -337,17 +343,17 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -383,20 +389,22 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -405,10 +413,10 @@ name = "gstreamer-sdp" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -441,10 +449,10 @@ name = "gstreamer-webrtc" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -502,7 +510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -568,7 +576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -652,34 +660,34 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-rational" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num_cpus" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -688,7 +696,7 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -720,7 +728,7 @@ version = "0.9.53" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -741,9 +749,9 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -753,25 +761,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pin-project" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-internal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-lite" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -791,25 +799,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-error" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -818,9 +826,9 @@ name = "proc-macro-hack" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -830,7 +838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -841,15 +849,15 @@ name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -870,7 +878,7 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -907,9 +915,9 @@ name = "rustversion" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -973,14 +981,14 @@ name = "serde_derive" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -990,7 +998,7 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1011,31 +1019,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1045,9 +1054,9 @@ name = "syn-mid" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1057,7 +1066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1084,9 +1093,9 @@ name = "thiserror-impl" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1101,9 +1110,9 @@ dependencies = [ "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1122,7 +1131,7 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1145,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "url" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1165,7 +1174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasi" -version = "0.7.0" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1174,17 +1183,17 @@ version = "0.1.0" dependencies = [ "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-tungstenite 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-tungstenite 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-webrtc 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1227,11 +1236,12 @@ dependencies = [ [metadata] "checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" -"checksum async-native-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f073e25c972058b3a98e74bfbd2297ec0a4b2ed7b9c3f7b1e66d976601b98a8" +"checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" "checksum async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e" -"checksum async-task 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d22dc86693d375d2733b536fd8914bea0fa93adf4b1e6bcbd9c7c500cb62d920" -"checksum async-tungstenite 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d35b7b512425d228d0366885b417ae3884930673ac1265764f1a8389dd2b00c4" +"checksum async-task 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f534e76ca33eaa82bc8da5adb1b9e94a16f6fa217b78e9b400094dbbf844f9" +"checksum async-tungstenite 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37250bc739b253cf2d861047a7bcaad1d46d1d027070b44dbeab9168b40cf407" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" @@ -1240,7 +1250,7 @@ dependencies = [ "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" @@ -1267,11 +1277,11 @@ dependencies = [ "checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" "checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -"checksum glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "121c502fc6895e62d2ce084e677d3289ccbdd7f56edd4ac9a5ab8bd95d4a8670" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27bafffe3fc615449d5a87705f93f6fe4fcf749662b9d08cc9d5451f6c1b0f21" "checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" "checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" -"checksum gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d9ea04f6e746e90d979eaf5b55a9125fd159e58959f203a2f3fbc4b2a93b77" +"checksum gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bbd828ab7aa022faec4248f3cf06891f6e02e7ca9271a5c0e5253eaf83b5259f" "checksum gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "674df58b85cb077a357c581c29796fbeb5aa36e8362269807a11f938e5c7b973" "checksum gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" "checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" @@ -1291,7 +1301,7 @@ dependencies = [ "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" "checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" @@ -1299,11 +1309,11 @@ dependencies = [ "checksum muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" -"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" -"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" -"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" @@ -1311,19 +1321,19 @@ dependencies = [ "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" "checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" -"checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" -"checksum pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" +"checksum pin-project 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "75fca1c4ff21f60ca2d37b80d72b63dab823a9d19d3cda3a81d18bc03f0ba8c5" +"checksum pin-project-internal 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6544cd4e4ecace61075a6ec78074beeef98d58aa9a3d07d053d993b2946a90d6" +"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro-error 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "53c98547ceaea14eeb26fcadf51dc70d01a2479a7839170eae133721105e4428" -"checksum proc-macro-error-attr 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c2bf5d493cf5d3e296beccfd61794e445e830dfc8070a9c248ad3ee071392c6c" +"checksum proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1b79a464461615532fcc8a6ed8296fa66cc12350c18460ab3f4594a6cee0fcb6" +"checksum proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "23832e5eae6bac56bbac190500eef1aaede63776b5cd131eaa4ee7fe120cd892" "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" +"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" @@ -1340,13 +1350,13 @@ dependencies = [ "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" -"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +"checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" +"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" -"checksum structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "884ae79d6aad1e738f4a70dff314203fd498490a63ebc4d03ea83323c40b7b72" -"checksum structopt-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a97f829a34a0a9d5b353a881025a23b8c9fd09d46be6045df6b22920dbd7a93" -"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" +"checksum structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713" +"checksum structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a" +"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" "checksum syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" @@ -1355,14 +1365,14 @@ dependencies = [ "checksum tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" +"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" +"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs index 4f25d26566..dfe80326a4 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs +++ b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs @@ -191,15 +191,9 @@ impl App { .get_by_name("audio-mixer") .expect("can't find audio-mixer"); + // Create a stream for handling the GStreamer message asynchronously let bus = pipeline.get_bus().unwrap(); - - // Send our bus messages via a futures channel to be handled asynchronously - let (send_gst_msg_tx, send_gst_msg_rx) = mpsc::unbounded::(); - let send_gst_msg_tx = Mutex::new(send_gst_msg_tx); - bus.set_sync_handler(move |_, msg| { - let _ = send_gst_msg_tx.lock().unwrap().unbounded_send(msg.clone()); - gst::BusSyncReply::Drop - }); + let send_gst_msg_rx = gst::BusStream::new(&bus); // Channel for outgoing WebSocket messages from other threads let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); diff --git a/webrtc/sendrecv/gst-rust/Cargo.lock b/webrtc/sendrecv/gst-rust/Cargo.lock index 927c73c8c3..2eefc9ea3b 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.lock +++ b/webrtc/sendrecv/gst-rust/Cargo.lock @@ -8,9 +8,9 @@ checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" [[package]] name = "async-native-tls" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f073e25c972058b3a98e74bfbd2297ec0a4b2ed7b9c3f7b1e66d976601b98a8" +checksum = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" dependencies = [ "async-std", "native-tls", @@ -44,18 +44,19 @@ dependencies = [ [[package]] name = "async-task" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22dc86693d375d2733b536fd8914bea0fa93adf4b1e6bcbd9c7c500cb62d920" +checksum = "a9f534e76ca33eaa82bc8da5adb1b9e94a16f6fa217b78e9b400094dbbf844f9" dependencies = [ - "crossbeam-utils", + "libc", + "winapi 0.3.8", ] [[package]] name = "async-tungstenite" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35b7b512425d228d0366885b417ae3884930673ac1265764f1a8389dd2b00c4" +checksum = "37250bc739b253cf2d861047a7bcaad1d46d1d027070b44dbeab9168b40cf407" dependencies = [ "async-native-tls", "async-std", @@ -71,6 +72,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + [[package]] name = "base64" version = "0.11.0" @@ -137,9 +144,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" [[package]] name = "cfg-if" @@ -199,7 +206,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" dependencies = [ - "autocfg", + "autocfg 0.1.7", "cfg-if", "crossbeam-utils", "lazy_static", @@ -213,7 +220,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" dependencies = [ - "autocfg", + "autocfg 0.1.7", "cfg-if", "lazy_static", ] @@ -378,9 +385,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ "cfg-if", "libc", @@ -389,9 +396,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c502fc6895e62d2ce084e677d3289ccbdd7f56edd4ac9a5ab8bd95d4a8670" +checksum = "27bafffe3fc615449d5a87705f93f6fe4fcf749662b9d08cc9d5451f6c1b0f21" dependencies = [ "bitflags", "futures-channel", @@ -428,13 +435,15 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d9ea04f6e746e90d979eaf5b55a9125fd159e58959f203a2f3fbc4b2a93b77" +checksum = "bbd828ab7aa022faec4248f3cf06891f6e02e7ca9271a5c0e5253eaf83b5259f" dependencies = [ "bitflags", "cfg-if", + "futures-channel", "futures-core", + "futures-util", "glib", "glib-sys", "gobject-sys", @@ -633,9 +642,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" [[package]] name = "memoffset" @@ -725,39 +734,39 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ - "autocfg", + "autocfg 1.0.0", "num-traits", ] [[package]] name = "num-rational" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" +checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" dependencies = [ - "autocfg", + "autocfg 1.0.0", "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ - "autocfg", + "autocfg 1.0.0", ] [[package]] name = "num_cpus" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" +checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" dependencies = [ "hermit-abi", "libc", @@ -765,9 +774,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" [[package]] name = "opaque-debug" @@ -801,7 +810,7 @@ version = "0.9.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" dependencies = [ - "autocfg", + "autocfg 0.1.7", "cc", "libc", "pkg-config", @@ -838,18 +847,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" +checksum = "75fca1c4ff21f60ca2d37b80d72b63dab823a9d19d3cda3a81d18bc03f0ba8c5" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" +checksum = "6544cd4e4ecace61075a6ec78074beeef98d58aa9a3d07d053d993b2946a90d6" dependencies = [ "proc-macro2", "quote", @@ -858,9 +867,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" +checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" [[package]] name = "pin-utils" @@ -882,9 +891,9 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "proc-macro-error" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c98547ceaea14eeb26fcadf51dc70d01a2479a7839170eae133721105e4428" +checksum = "1b79a464461615532fcc8a6ed8296fa66cc12350c18460ab3f4594a6cee0fcb6" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -895,9 +904,9 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2bf5d493cf5d3e296beccfd61794e445e830dfc8070a9c248ad3ee071392c6c" +checksum = "23832e5eae6bac56bbac190500eef1aaede63776b5cd131eaa4ee7fe120cd892" dependencies = [ "proc-macro2", "quote", @@ -925,9 +934,9 @@ checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" [[package]] name = "proc-macro2" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" +checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" dependencies = [ "unicode-xid", ] @@ -943,9 +952,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", @@ -1094,9 +1103,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +checksum = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" dependencies = [ "itoa", "ryu", @@ -1105,9 +1114,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ "block-buffer", "digest", @@ -1129,19 +1138,20 @@ checksum = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" [[package]] name = "structopt" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884ae79d6aad1e738f4a70dff314203fd498490a63ebc4d03ea83323c40b7b72" +checksum = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713" dependencies = [ "clap", + "lazy_static", "structopt-derive", ] [[package]] name = "structopt-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a97f829a34a0a9d5b353a881025a23b8c9fd09d46be6045df6b22920dbd7a93" +checksum = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a" dependencies = [ "heck", "proc-macro-error", @@ -1152,9 +1162,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" +checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" dependencies = [ "proc-macro2", "quote", @@ -1251,9 +1261,9 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ "smallvec", ] @@ -1278,9 +1288,9 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "url" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ "idna", "matches", @@ -1301,9 +1311,9 @@ checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" [[package]] name = "wasi" -version = "0.7.0" +version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "webrtc-app" @@ -1313,6 +1323,7 @@ dependencies = [ "async-std", "async-tungstenite", "futures", + "glib", "gstreamer", "gstreamer-sdp", "gstreamer-webrtc", diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index 2072def02c..c35cce48ff 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -133,15 +133,9 @@ impl App { webrtcbin.set_property_from_str("stun-server", STUN_SERVER); webrtcbin.set_property_from_str("bundle-policy", "max-bundle"); + // Create a stream for handling the GStreamer message asynchronously let bus = pipeline.get_bus().unwrap(); - - // Send our bus messages via a futures channel to be handled asynchronously - let (send_gst_msg_tx, send_gst_msg_rx) = mpsc::unbounded::(); - let send_gst_msg_tx = Mutex::new(send_gst_msg_tx); - bus.set_sync_handler(move |_, msg| { - let _ = send_gst_msg_tx.lock().unwrap().unbounded_send(msg.clone()); - gst::BusSyncReply::Drop - }); + let send_gst_msg_rx = gst::BusStream::new(&bus); // Channel for outgoing WebSocket messages from other threads let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); From d8e76871328a9d6b128ed66806898408abc58fb9 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 28 Jan 2020 00:03:39 +1100 Subject: [PATCH 325/412] janus: Add options near the top Add some script configuration options to choose between VP8 and H.264 near the top, to modify the video input source, and to enable/disable RTX support --- webrtc/janus/janusvideoroom.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/webrtc/janus/janusvideoroom.py b/webrtc/janus/janusvideoroom.py index 80ee47fef5..c1b2cd5a26 100644 --- a/webrtc/janus/janusvideoroom.py +++ b/webrtc/janus/janusvideoroom.py @@ -15,6 +15,15 @@ from websockets.exceptions import ConnectionClosed import attr +# Set to False to send H.264 +DO_VP8 = True +# Set to False to disable RTX (lost packet retransmission) +DO_RTX = True +# Choose the video source: +VIDEO_SRC="videotestsrc pattern=ball" +# VIDEO_SRC="v4l2src" + + @attr.s class JanusEvent: sender = attr.ib(validator=attr.validators.instance_of(int)) @@ -66,8 +75,6 @@ from gi.repository import GstWebRTC gi.require_version('GstSdp', '1.0') from gi.repository import GstSdp -DO_VP8 = True - if DO_VP8: ( encoder, payloader, rtp_encoding) = ( "vp8enc target-bitrate=500000", "rtpvp8pay", "VP8" ) else: @@ -75,9 +82,9 @@ else: PIPELINE_DESC = ''' webrtcbin name=sendrecv stun-server=stun://stun.l.google.com:19302 - videotestsrc pattern=ball ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! + {} ! video/x-raw,width=640,height=480 ! videoconvert ! queue ! {} ! {} ! queue ! application/x-rtp,media=video,encoding-name={},payload=96 ! sendrecv. -'''.format(encoder, payloader, rtp_encoding) +'''.format(VIDEO_SRC, encoder, payloader, rtp_encoding) def transaction_id(): return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) @@ -328,6 +335,10 @@ class WebRTCClient: self.webrtc.connect('on-negotiation-needed', self.on_negotiation_needed) self.webrtc.connect('on-ice-candidate', self.send_ice_candidate_message) self.webrtc.connect('pad-added', self.on_incoming_stream) + + trans = self.webrtc.emit('get-transceiver', 0) + if DO_RTX: + trans.set_property ('do-nack', True) self.pipe.set_state(Gst.State.PLAYING) def extract_ice_from_sdp(self, sdp): From 1f1233064f759c4d839311be6c4e8efe80684749 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 28 Jan 2020 00:04:27 +1100 Subject: [PATCH 326/412] janus: Add picture-id-mode=2 to VP8 payloading This writes an extended header and Picture-ID into each RTP packet which makes Janus able to detect which frames are keyframes and to request replacement keyframes. --- webrtc/janus/janusvideoroom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/janus/janusvideoroom.py b/webrtc/janus/janusvideoroom.py index c1b2cd5a26..4372a6361f 100644 --- a/webrtc/janus/janusvideoroom.py +++ b/webrtc/janus/janusvideoroom.py @@ -76,7 +76,7 @@ gi.require_version('GstSdp', '1.0') from gi.repository import GstSdp if DO_VP8: - ( encoder, payloader, rtp_encoding) = ( "vp8enc target-bitrate=500000", "rtpvp8pay", "VP8" ) + ( encoder, payloader, rtp_encoding) = ( "vp8enc target-bitrate=100000 overshoot=25 undershoot=100 deadline=33000 keyframe-max-dist=1", "rtpvp8pay picture-id-mode=2", "VP8" ) else: ( encoder, payloader, rtp_encoding) = ( "x264enc", "rtph264pay", "H264" ) From 699b830213996be7d7d2362477802e02557d0f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 1 Feb 2020 15:21:08 +0200 Subject: [PATCH 327/412] Update Rust examples to async-tungstenite 0.4 --- .../multiparty-sendrecv/gst-rust/Cargo.lock | 147 +++++++++--------- .../multiparty-sendrecv/gst-rust/Cargo.toml | 3 +- .../multiparty-sendrecv/gst-rust/src/main.rs | 3 +- webrtc/sendrecv/gst-rust/Cargo.lock | 92 +++++------ webrtc/sendrecv/gst-rust/Cargo.toml | 3 +- webrtc/sendrecv/gst-rust/src/main.rs | 3 +- 6 files changed, 118 insertions(+), 133 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock index fb1183fdec..7fce37ce1c 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock @@ -12,7 +12,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -20,7 +20,7 @@ name = "async-std" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-task 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "async-task" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -50,15 +50,15 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tungstenite 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -112,12 +112,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bytes" -version = "0.4.12" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "c2-chacha" @@ -389,7 +385,7 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -416,7 +412,7 @@ dependencies = [ "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -452,7 +448,7 @@ dependencies = [ "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -490,12 +486,12 @@ dependencies = [ [[package]] name = "http" -version = "0.1.21" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -515,10 +511,10 @@ dependencies = [ [[package]] name = "input_buffer" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -531,7 +527,7 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -639,9 +635,9 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.27 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -706,7 +702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.10.26" +version = "0.10.27" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -714,7 +710,7 @@ dependencies = [ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -724,10 +720,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.53" +version = "0.9.54" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -761,15 +757,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pin-project" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pin-project-internal 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-internal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -799,26 +795,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-error" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -912,7 +908,7 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -991,7 +987,7 @@ name = "serde_json" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1014,26 +1010,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1051,7 +1047,7 @@ dependencies = [ [[package]] name = "syn-mid" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1082,15 +1078,15 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thiserror-impl" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1100,15 +1096,15 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1134,7 +1130,7 @@ name = "unicode-normalization" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1183,17 +1179,16 @@ version = "0.1.0" dependencies = [ "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-tungstenite 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-tungstenite 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-webrtc 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1238,8 +1233,8 @@ dependencies = [ "checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" "checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" "checksum async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e" -"checksum async-task 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f534e76ca33eaa82bc8da5adb1b9e94a16f6fa217b78e9b400094dbbf844f9" -"checksum async-tungstenite 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37250bc739b253cf2d861047a7bcaad1d46d1d027070b44dbeab9168b40cf407" +"checksum async-task 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f20c6fda19d0fc02406779587ca4f9a4171cd32e4a5bda0bd016f0a1334c8d4a" +"checksum async-tungstenite 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f1a7fca4f1ccd1fe926bec21a4ba6259d3d91f04d67a9dd76bd549366be5bef" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" @@ -1248,7 +1243,7 @@ dependencies = [ "checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" @@ -1281,7 +1276,7 @@ dependencies = [ "checksum glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27bafffe3fc615449d5a87705f93f6fe4fcf749662b9d08cc9d5451f6c1b0f21" "checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" "checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" -"checksum gstreamer 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bbd828ab7aa022faec4248f3cf06891f6e02e7ca9271a5c0e5253eaf83b5259f" +"checksum gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f439859b5f3310518adc0aea572ac89ddd94f8ba3af4626d6156733671599629" "checksum gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "674df58b85cb077a357c581c29796fbeb5aa36e8362269807a11f938e5c7b973" "checksum gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" "checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" @@ -1289,12 +1284,12 @@ dependencies = [ "checksum gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" -"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" +"checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" @@ -1315,20 +1310,20 @@ dependencies = [ "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" "checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" +"checksum openssl 0.10.27 (registry+https://github.com/rust-lang/crates.io-index)" = "e176a45fedd4c990e26580847a525e39e16ec32ac78957dbf62ded31b3abfd6f" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" +"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" "checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "75fca1c4ff21f60ca2d37b80d72b63dab823a9d19d3cda3a81d18bc03f0ba8c5" -"checksum pin-project-internal 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6544cd4e4ecace61075a6ec78074beeef98d58aa9a3d07d053d993b2946a90d6" +"checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +"checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" "checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro-error 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1b79a464461615532fcc8a6ed8296fa66cc12350c18460ab3f4594a6cee0fcb6" -"checksum proc-macro-error-attr 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "23832e5eae6bac56bbac190500eef1aaede63776b5cd131eaa4ee7fe120cd892" +"checksum proc-macro-error 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" +"checksum proc-macro-error-attr 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" "checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" @@ -1340,7 +1335,7 @@ dependencies = [ "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" +"checksum rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" @@ -1353,16 +1348,16 @@ dependencies = [ "checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" -"checksum structopt 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713" -"checksum structopt-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a" +"checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" +"checksum structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" +"checksum structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" "checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" -"checksum syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" +"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" -"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" -"checksum tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" +"checksum thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" +"checksum thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" +"checksum tungstenite 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08f33f14c64af9dfc6e50e6812261178d6bd434c0e501f87c7683b8aaa9eb8d3" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml index ec353310fa..94e6533f6c 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml @@ -9,9 +9,8 @@ futures = "0.3" async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" -url = "2" rand = "0.7" -async-tungstenite = { version = "0.3", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.4", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } diff --git a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs index dfe80326a4..995b4bd361 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs +++ b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs @@ -1010,8 +1010,7 @@ async fn async_main() -> Result<(), anyhow::Error> { let args = Args::from_args(); // Connect to the given server - let url = url::Url::parse(&args.server)?; - let (mut ws, _) = async_tungstenite::async_std::connect_async(url).await?; + let (mut ws, _) = async_tungstenite::async_std::connect_async(&args.server).await?; println!("connected"); diff --git a/webrtc/sendrecv/gst-rust/Cargo.lock b/webrtc/sendrecv/gst-rust/Cargo.lock index 2eefc9ea3b..cebc8e6043 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.lock +++ b/webrtc/sendrecv/gst-rust/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "async-task" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f534e76ca33eaa82bc8da5adb1b9e94a16f6fa217b78e9b400094dbbf844f9" +checksum = "f20c6fda19d0fc02406779587ca4f9a4171cd32e4a5bda0bd016f0a1334c8d4a" dependencies = [ "libc", "winapi 0.3.8", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37250bc739b253cf2d861047a7bcaad1d46d1d027070b44dbeab9168b40cf407" +checksum = "1f1a7fca4f1ccd1fe926bec21a4ba6259d3d91f04d67a9dd76bd549366be5bef" dependencies = [ "async-native-tls", "async-std", @@ -125,13 +125,9 @@ checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" [[package]] name = "bytes" -version = "0.4.12" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] name = "c2-chacha" @@ -435,9 +431,9 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd828ab7aa022faec4248f3cf06891f6e02e7ca9271a5c0e5253eaf83b5259f" +checksum = "f439859b5f3310518adc0aea572ac89ddd94f8ba3af4626d6156733671599629" dependencies = [ "bitflags", "cfg-if", @@ -544,9 +540,9 @@ dependencies = [ [[package]] name = "http" -version = "0.1.21" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" dependencies = [ "bytes", "fnv", @@ -572,9 +568,9 @@ dependencies = [ [[package]] name = "input_buffer" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" dependencies = [ "bytes", ] @@ -590,9 +586,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "kernel32-sys" @@ -786,9 +782,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" -version = "0.10.26" +version = "0.10.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" +checksum = "e176a45fedd4c990e26580847a525e39e16ec32ac78957dbf62ded31b3abfd6f" dependencies = [ "bitflags", "cfg-if", @@ -806,11 +802,11 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.53" +version = "0.9.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" +checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" dependencies = [ - "autocfg 0.1.7", + "autocfg 1.0.0", "cc", "libc", "pkg-config", @@ -847,18 +843,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75fca1c4ff21f60ca2d37b80d72b63dab823a9d19d3cda3a81d18bc03f0ba8c5" +checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6544cd4e4ecace61075a6ec78074beeef98d58aa9a3d07d053d993b2946a90d6" +checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" dependencies = [ "proc-macro2", "quote", @@ -891,9 +887,9 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "proc-macro-error" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b79a464461615532fcc8a6ed8296fa66cc12350c18460ab3f4594a6cee0fcb6" +checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -904,9 +900,9 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23832e5eae6bac56bbac190500eef1aaede63776b5cd131eaa4ee7fe120cd892" +checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" dependencies = [ "proc-macro2", "quote", @@ -1017,9 +1013,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" +checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" dependencies = [ "proc-macro2", "quote", @@ -1132,15 +1128,15 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" [[package]] name = "structopt" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713" +checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" dependencies = [ "clap", "lazy_static", @@ -1149,9 +1145,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a" +checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" dependencies = [ "heck", "proc-macro-error", @@ -1173,9 +1169,9 @@ dependencies = [ [[package]] name = "syn-mid" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ "proc-macro2", "quote", @@ -1207,18 +1203,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +checksum = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" +checksum = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" dependencies = [ "proc-macro2", "quote", @@ -1227,9 +1223,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" +checksum = "08f33f14c64af9dfc6e50e6812261178d6bd434c0e501f87c7683b8aaa9eb8d3" dependencies = [ "base64", "byteorder", @@ -1323,7 +1319,6 @@ dependencies = [ "async-std", "async-tungstenite", "futures", - "glib", "gstreamer", "gstreamer-sdp", "gstreamer-webrtc", @@ -1332,7 +1327,6 @@ dependencies = [ "serde_derive", "serde_json", "structopt", - "url", ] [[package]] diff --git a/webrtc/sendrecv/gst-rust/Cargo.toml b/webrtc/sendrecv/gst-rust/Cargo.toml index ec353310fa..94e6533f6c 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.toml +++ b/webrtc/sendrecv/gst-rust/Cargo.toml @@ -9,9 +9,8 @@ futures = "0.3" async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" -url = "2" rand = "0.7" -async-tungstenite = { version = "0.3", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.4", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index c35cce48ff..48f9c38674 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -639,8 +639,7 @@ async fn async_main() -> Result<(), anyhow::Error> { let args = Args::from_args(); // Connect to the given server - let url = url::Url::parse(&args.server)?; - let (mut ws, _) = async_tungstenite::async_std::connect_async(url).await?; + let (mut ws, _) = async_tungstenite::async_std::connect_async(&args.server).await?; println!("connected"); From d2236266dc62d6912bcecb9195129c1a1ce4bb5d Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Thu, 30 Jan 2020 14:46:05 +1100 Subject: [PATCH 328/412] Android: Update build for android example --- webrtc/android/app/build.gradle | 12 ++-- webrtc/android/gradlew | 100 ++++++++++++++++++-------------- webrtc/android/gradlew.bat | 14 ++--- 3 files changed, 67 insertions(+), 59 deletions(-) diff --git a/webrtc/android/app/build.gradle b/webrtc/android/app/build.gradle index e7def0cd53..9092448743 100644 --- a/webrtc/android/app/build.gradle +++ b/webrtc/android/app/build.gradle @@ -52,13 +52,15 @@ android { } afterEvaluate { - compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' - compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' + if (project.hasProperty('compileDebugJavaWithJavac')) + compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' + api fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' implementation 'com.android.support.constraint:constraint-layout:1.0.2' } diff --git a/webrtc/android/gradlew b/webrtc/android/gradlew index 9d82f78915..cccdd3d517 100755 --- a/webrtc/android/gradlew +++ b/webrtc/android/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,42 +6,6 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/webrtc/android/gradlew.bat b/webrtc/android/gradlew.bat index aec99730b4..e95643d6a2 100644 --- a/webrtc/android/gradlew.bat +++ b/webrtc/android/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line From c8e79c9671a627e12a299adcf2beb19d28c71344 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Fri, 21 Feb 2020 14:01:58 +1100 Subject: [PATCH 329/412] webrtc-sendrecv.py: Add a stun server Fixes https://github.com/centricular/gstwebrtc-demos/issues/160 --- webrtc/sendrecv/gst/webrtc-sendrecv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.py b/webrtc/sendrecv/gst/webrtc-sendrecv.py index 88870b12ca..b19bc3fa6f 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.py +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.py @@ -16,7 +16,7 @@ gi.require_version('GstSdp', '1.0') from gi.repository import GstSdp PIPELINE_DESC = ''' -webrtcbin name=sendrecv bundle-policy=max-bundle +webrtcbin name=sendrecv bundle-policy=max-bundle stun-server=stun://stun.l.google.com:19302 videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! queue ! application/x-rtp,media=video,encoding-name=VP8,payload=97 ! sendrecv. audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! From 8b90dd949a89f5ded58a0544b672e550cea97c29 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 2 Mar 2020 18:54:59 +0530 Subject: [PATCH 330/412] Cerbero has moved from gnutls+openssl to only openssl --- playback/player/ios/GstPlay/gst_ios_init.h | 2 +- playback/player/ios/GstPlay/gst_ios_init.m | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/playback/player/ios/GstPlay/gst_ios_init.h b/playback/player/ios/GstPlay/gst_ios_init.h index 42787128d8..4c12972697 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.h +++ b/playback/player/ios/GstPlay/gst_ios_init.h @@ -30,7 +30,7 @@ G_PASTE(g_io_, G_PASTE(name, _load)) (NULL) //#define GST_IOS_PLUGINS_GES -#define GST_IOS_GIO_MODULE_GNUTLS +#define GST_IOS_GIO_MODULE_OPENSSL void gst_ios_init (void); diff --git a/playback/player/ios/GstPlay/gst_ios_init.m b/playback/player/ios/GstPlay/gst_ios_init.m index 592dbf8aea..f5d9f5f50e 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.m +++ b/playback/player/ios/GstPlay/gst_ios_init.m @@ -528,8 +528,8 @@ GST_PLUGIN_STATIC_DECLARE(rtmp); GST_PLUGIN_STATIC_DECLARE(nle); #endif -#if defined(GST_IOS_GIO_MODULE_GNUTLS) - GST_G_IO_MODULE_DECLARE(gnutls); +#if defined(GST_IOS_GIO_MODULE_OPENSSL) + GST_G_IO_MODULE_DECLARE(openssl); #endif void @@ -564,8 +564,8 @@ gst_ios_init (void) ca_certificates = g_build_filename (resources_dir, "ssl", "certs", "ca-certificates.crt", NULL); g_setenv ("CA_CERTIFICATES", ca_certificates, TRUE); -#if defined(GST_IOS_GIO_MODULE_GNUTLS) - GST_G_IO_MODULE_LOAD(gnutls); +#if defined(GST_IOS_GIO_MODULE_OPENSSL) + GST_G_IO_MODULE_LOAD(openssl); #endif if (ca_certificates) { From 5bf67feae8f4755862e571674ce0c8cf78d9b45b Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Thu, 5 Mar 2020 03:03:17 +1100 Subject: [PATCH 331/412] 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. --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 160 ++++++++++++++++++++------ webrtc/sendrecv/js/webrtc.js | 60 ++++++---- 2 files changed, 165 insertions(+), 55 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index c2bb55c722..39c4ef9f9a 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -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; diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index 124c10d7cf..471085eeff 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -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; } From 65db695212f9cd09fafb37e2ac19b5a42137faa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 24 Mar 2020 12:57:17 +0200 Subject: [PATCH 332/412] Set TURN server in Rust sendrecv example too Previously it was only in the multiparty example. --- webrtc/sendrecv/gst-rust/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index 48f9c38674..92e71b29b6 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -24,6 +24,7 @@ use serde_derive::{Deserialize, Serialize}; use anyhow::{anyhow, bail, Context}; const STUN_SERVER: &str = "stun://stun.l.google.com:19302"; +const TURN_SERVER: &str = "turn://foo:bar@webrtc.nirbheek.in:3478"; // upgrade weak reference or return #[macro_export] @@ -131,6 +132,7 @@ impl App { // Set some properties on webrtcbin webrtcbin.set_property_from_str("stun-server", STUN_SERVER); + webrtcbin.set_property_from_str("turn-server", TURN_SERVER); webrtcbin.set_property_from_str("bundle-policy", "max-bundle"); // Create a stream for handling the GStreamer message asynchronously From 804c0c2f5ead8264414e2a9b0cb46dc42124e56d Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2020 13:49:41 +0300 Subject: [PATCH 333/412] gst-indent --- .../gst/mp-webrtc-sendrecv.c | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index d1d3561bdb..6e66fbd162 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -17,23 +17,24 @@ #include -enum AppState { +enum AppState +{ APP_STATE_UNKNOWN = 0, - APP_STATE_ERROR = 1, /* generic error */ + APP_STATE_ERROR = 1, /* generic error */ SERVER_CONNECTING = 1000, SERVER_CONNECTION_ERROR, - SERVER_CONNECTED, /* Ready to register */ + SERVER_CONNECTED, /* Ready to register */ SERVER_REGISTERING = 2000, SERVER_REGISTRATION_ERROR, - SERVER_REGISTERED, /* Ready to call a peer */ - SERVER_CLOSED, /* server connection closed by us or the server */ + SERVER_REGISTERED, /* Ready to call a peer */ + SERVER_CLOSED, /* server connection closed by us or the server */ ROOM_JOINING = 3000, ROOM_JOIN_ERROR, ROOM_JOINED, ROOM_CALL_NEGOTIATING = 4000, /* negotiating with some or all peers */ - ROOM_CALL_OFFERING, /* when we're the one sending the offer */ - ROOM_CALL_ANSWERING, /* when we're the one answering an offer */ - ROOM_CALL_STARTED, /* in a call with some or all peers */ + ROOM_CALL_OFFERING, /* when we're the one sending the offer */ + ROOM_CALL_ANSWERING, /* when we're the one answering an offer */ + ROOM_CALL_STARTED, /* in a call with some or all peers */ ROOM_CALL_STOPPING, ROOM_CALL_STOPPED, ROOM_CALL_ERROR, @@ -51,12 +52,14 @@ static gchar *local_id = NULL; static gchar *room_id = NULL; static gboolean strict_ssl = TRUE; -static GOptionEntry entries[] = -{ - { "name", 0, 0, G_OPTION_ARG_STRING, &local_id, "Name we will send to the server", "ID" }, - { "room-id", 0, 0, G_OPTION_ARG_STRING, &room_id, "Room name to join or create", "ID" }, - { "server", 0, 0, G_OPTION_ARG_STRING, &server_url, "Signalling server to connect to", "URL" }, - { NULL } +static GOptionEntry entries[] = { + {"name", 0, 0, G_OPTION_ARG_STRING, &local_id, + "Name we will send to the server", "ID"}, + {"room-id", 0, 0, G_OPTION_ARG_STRING, &room_id, + "Room name to join or create", "ID"}, + {"server", 0, 0, G_OPTION_ARG_STRING, &server_url, + "Signalling server to connect to", "URL"}, + {NULL} }; static gint @@ -97,7 +100,7 @@ cleanup_and_quit_loop (const gchar * msg, enum AppState state) return G_SOURCE_REMOVE; } -static gchar* +static gchar * get_string_from_json_object (JsonObject * object) { JsonNode *root; @@ -117,8 +120,8 @@ get_string_from_json_object (JsonObject * object) } static void -handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, - const char * sink_name) +handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, + const char *sink_name) { GstPad *qpad; GstElement *q, *conv, *sink; @@ -415,8 +418,7 @@ start_pipeline (void) * we can preroll early. */ pipeline = gst_parse_launch ("tee name=audiotee ! queue ! fakesink " "audiotestsrc is-live=true wave=red-noise ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS(96) " ! audiotee. ", - &error); + "queue ! " RTP_CAPS_OPUS (96) " ! audiotee. ", &error); if (error) { g_printerr ("Failed to parse launch: %s\n", error->message); @@ -525,8 +527,7 @@ do_join_room (const gchar * text) g_print ("Room joined\n"); /* Start recording, but not transmitting */ if (!start_pipeline ()) { - cleanup_and_quit_loop ("ERROR: Failed to start pipeline", - ROOM_CALL_ERROR); + cleanup_and_quit_loop ("ERROR: Failed to start pipeline", ROOM_CALL_ERROR); return; } @@ -767,7 +768,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, case SOUP_WEBSOCKET_DATA_BINARY: g_printerr ("Received unknown binary message, ignoring\n"); return; - case SOUP_WEBSOCKET_DATA_TEXT: { + case SOUP_WEBSOCKET_DATA_TEXT:{ gsize size; const gchar *data = g_bytes_get_data (message, &size); /* Convert to NULL-terminated string */ @@ -782,7 +783,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, if (g_strcmp0 (text, "HELLO") == 0) { /* May fail asynchronously */ do_registration (); - /* Room-related message */ + /* Room-related message */ } else if (g_str_has_prefix (text, "ROOM_")) { /* Room joined, now we can start negotiation */ if (g_str_has_prefix (text, "ROOM_OK ")) { @@ -811,7 +812,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, peers = g_list_remove (peers, peer_id); g_print ("Peer %s has left the room\n", peer_id); remove_peer_from_pipeline (peer_id); - g_free ((gchar*) peer_id); + g_free ((gchar *) peer_id); /* TODO: cleanup pipeline */ } else { g_printerr ("WARNING: Ignoring unknown message %s\n", text); @@ -820,7 +821,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, } else { goto err; } - /* Handle errors */ + /* Handle errors */ } else if (g_str_has_prefix (text, "ERROR")) { handle_error_message (text); } else { @@ -842,7 +843,7 @@ err: static void on_server_connected (SoupSession * session, GAsyncResult * res, - SoupMessage *msg) + SoupMessage * msg) { GError *error = NULL; @@ -875,7 +876,7 @@ connect_to_websocket_server_async (void) SoupLogger *logger; SoupMessage *message; SoupSession *session; - const char *https_aliases[] = {"wss", NULL}; + const char *https_aliases[] = { "wss", NULL }; session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, strict_ssl, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, @@ -904,7 +905,8 @@ check_plugins (void) GstPlugin *plugin; GstRegistry *registry; const gchar *needed[] = { "opus", "nice", "webrtc", "dtls", "srtp", - "rtpmanager", "audiotestsrc", NULL}; + "rtpmanager", "audiotestsrc", NULL + }; registry = gst_registry_get (); ret = TRUE; From ca96b6de860349aa7eba26bd55a59d01664a8025 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2020 13:49:48 +0300 Subject: [PATCH 334/412] gst-indent --- webrtc/sendonly/webrtc-unidirectional-h264.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index e71ff39afb..e6f7a59ae0 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -186,7 +186,9 @@ create_receiver_entry (SoupWebsocketConnection * connection) G_CALLBACK (soup_websocket_message_cb), (gpointer) receiver_entry); error = NULL; - receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " + receiver_entry->pipeline = + gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" + STUN_SERVER " " "v4l2src ! videorate ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " "rtph264pay config-interval=-1 name=payloader ! " "application/x-rtp,media=video,encoding-name=H264,payload=" @@ -201,7 +203,8 @@ create_receiver_entry (SoupWebsocketConnection * connection) gst_bin_get_by_name (GST_BIN (receiver_entry->pipeline), "webrtcbin"); g_assert (receiver_entry->webrtcbin != NULL); - g_signal_emit_by_name (receiver_entry->webrtcbin, "get-transceivers", &transceivers); + g_signal_emit_by_name (receiver_entry->webrtcbin, "get-transceivers", + &transceivers); g_assert (transceivers != NULL && transceivers->len > 0); trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; From 2557eab9d579e3de04a00b2c0b91266fee47aace Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2020 13:49:55 +0300 Subject: [PATCH 335/412] gst-indent --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 110 +++++++++++++------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 39c4ef9f9a..584863d0c4 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -18,16 +18,17 @@ #include -enum AppState { +enum AppState +{ APP_STATE_UNKNOWN = 0, - APP_STATE_ERROR = 1, /* generic error */ + APP_STATE_ERROR = 1, /* generic error */ SERVER_CONNECTING = 1000, SERVER_CONNECTION_ERROR, - SERVER_CONNECTED, /* Ready to register */ + SERVER_CONNECTED, /* Ready to register */ SERVER_REGISTERING = 2000, SERVER_REGISTRATION_ERROR, - SERVER_REGISTERED, /* Ready to call a peer */ - SERVER_CLOSED, /* server connection closed by us or the server */ + SERVER_REGISTERED, /* Ready to call a peer */ + SERVER_CLOSED, /* server connection closed by us or the server */ PEER_CONNECTING = 3000, PEER_CONNECTION_ERROR, PEER_CONNECTED, @@ -49,13 +50,15 @@ 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 }, +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}, }; static gboolean @@ -84,7 +87,7 @@ cleanup_and_quit_loop (const gchar * msg, enum AppState state) return G_SOURCE_REMOVE; } -static gchar* +static gchar * get_string_from_json_object (JsonObject * object) { JsonNode *root; @@ -104,8 +107,8 @@ get_string_from_json_object (JsonObject * object) } static void -handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, - const char * sink_name) +handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, + const char *sink_name) { GstPad *qpad; GstElement *q, *conv, *resample, *sink; @@ -215,13 +218,14 @@ send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex, } static void -send_sdp_to_peer (GstWebRTCSessionDescription *desc) +send_sdp_to_peer (GstWebRTCSessionDescription * desc) { gchar *text; JsonObject *msg, *sdp; if (app_state < PEER_CALL_NEGOTIATING) { - cleanup_and_quit_loop ("Can't send SDP to peer, not in call", APP_STATE_ERROR); + cleanup_and_quit_loop ("Can't send SDP to peer, not in call", + APP_STATE_ERROR); return; } @@ -231,12 +235,10 @@ send_sdp_to_peer (GstWebRTCSessionDescription *desc) 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) { + } 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 { + } else { g_assert_not_reached (); } @@ -261,7 +263,7 @@ on_offer_created (GstPromise * promise, gpointer user_data) g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - g_assert_cmphex (gst_promise_wait(promise), ==, GST_PROMISE_RESULT_REPLIED); + g_assert_cmphex (gst_promise_wait (promise), ==, GST_PROMISE_RESULT_REPLIED); reply = gst_promise_get_reply (promise); gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); @@ -288,7 +290,8 @@ on_negotiation_needed (GstElement * element, gpointer user_data) g_free (msg); } else { GstPromise *promise; - promise = gst_promise_new_with_change_func (on_offer_created, user_data, NULL);; + promise = + gst_promise_new_with_change_func (on_offer_created, user_data, NULL);; g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); } } @@ -306,7 +309,7 @@ data_channel_on_error (GObject * dc, gpointer user_data) static void data_channel_on_open (GObject * dc, gpointer user_data) { - GBytes *bytes = g_bytes_new ("data", strlen("data")); + GBytes *bytes = g_bytes_new ("data", strlen ("data")); g_print ("data channel opened\n"); g_signal_emit_by_name (dc, "send-string", "Hi! from GStreamer"); g_signal_emit_by_name (dc, "send-data", bytes); @@ -320,7 +323,7 @@ data_channel_on_close (GObject * dc, gpointer user_data) } static void -data_channel_on_message_string (GObject * dc, gchar *str, gpointer user_data) +data_channel_on_message_string (GObject * dc, gchar * str, gpointer user_data) { g_print ("Received data channel message: %s\n", str); } @@ -328,18 +331,19 @@ data_channel_on_message_string (GObject * dc, gchar *str, gpointer user_data) static void connect_data_channel_signals (GObject * data_channel) { - g_signal_connect (data_channel, "on-error", G_CALLBACK (data_channel_on_error), - NULL); + g_signal_connect (data_channel, "on-error", + G_CALLBACK (data_channel_on_error), NULL); g_signal_connect (data_channel, "on-open", G_CALLBACK (data_channel_on_open), NULL); - g_signal_connect (data_channel, "on-close", G_CALLBACK (data_channel_on_close), - NULL); - g_signal_connect (data_channel, "on-message-string", G_CALLBACK (data_channel_on_message_string), - NULL); + g_signal_connect (data_channel, "on-close", + G_CALLBACK (data_channel_on_close), NULL); + g_signal_connect (data_channel, "on-message-string", + G_CALLBACK (data_channel_on_message_string), NULL); } static void -on_data_channel (GstElement * webrtc, GObject * data_channel, gpointer user_data) +on_data_channel (GstElement * webrtc, GObject * data_channel, + gpointer user_data) { connect_data_channel_signals (data_channel); receive_channel = data_channel; @@ -352,8 +356,7 @@ on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec, GstWebRTCICEGatheringState ice_gather_state; const gchar *new_state = "unknown"; - g_object_get (webrtcbin, "ice-gathering-state", &ice_gather_state, - NULL); + 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"; @@ -375,12 +378,12 @@ start_pipeline (void) GError *error = NULL; pipe1 = - gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER + gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " + STUN_SERVER "videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", - &error); + "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", &error); if (error) { g_printerr ("Failed to parse launch: %s\n", error->message); @@ -497,7 +500,7 @@ on_answer_created (GstPromise * promise, gpointer user_data) g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - g_assert_cmphex (gst_promise_wait(promise), ==, GST_PROMISE_RESULT_REPLIED); + 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); @@ -514,7 +517,7 @@ on_answer_created (GstPromise * promise, gpointer user_data) } static void -on_offer_received (GstSDPMessage *sdp) +on_offer_received (GstSDPMessage * sdp) { GstWebRTCSessionDescription *offer = NULL; GstPromise *promise; @@ -525,15 +528,13 @@ on_offer_received (GstSDPMessage *sdp) /* Set remote description on our pipeline */ { promise = gst_promise_new (); - g_signal_emit_by_name (webrtc1, "set-remote-description", offer, - promise); + 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); + promise = gst_promise_new_with_change_func (on_answer_created, NULL, NULL); g_signal_emit_by_name (webrtc1, "create-answer", NULL, promise); } @@ -548,7 +549,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, case SOUP_WEBSOCKET_DATA_BINARY: g_printerr ("Received unknown binary message, ignoring\n"); return; - case SOUP_WEBSOCKET_DATA_TEXT: { + case SOUP_WEBSOCKET_DATA_TEXT:{ gsize size; const gchar *data = g_bytes_get_data (message, &size); /* Convert to NULL-terminated string */ @@ -573,7 +574,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); goto out; } - /* Call has been setup by the server, now we can start negotiation */ + /* Call has been setup by the server, now we can start negotiation */ } else if (g_strcmp0 (text, "SESSION_OK") == 0) { if (app_state != PEER_CONNECTING) { cleanup_and_quit_loop ("ERROR: Received SESSION_OK when not calling", @@ -586,7 +587,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, if (!start_pipeline ()) cleanup_and_quit_loop ("ERROR: failed to start pipeline", PEER_CALL_ERROR); - /* Handle errors */ + /* Handle errors */ } else if (g_str_has_prefix (text, "ERROR")) { switch (app_state) { case SERVER_CONNECTING: @@ -605,7 +606,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, app_state = APP_STATE_ERROR; } cleanup_and_quit_loop (text, 0); - /* Look for JSON messages containing SDP and ICE candidates */ + /* Look for JSON messages containing SDP and ICE candidates */ } else { JsonNode *root; JsonObject *object, *child; @@ -659,7 +660,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, 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 (); @@ -669,8 +670,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, gst_promise_unref (promise); } app_state = PEER_CALL_STARTED; - } - else { + } else { g_print ("Received offer:\n%s\n", text); on_offer_received (sdp); } @@ -698,7 +698,7 @@ out: static void on_server_connected (SoupSession * session, GAsyncResult * res, - SoupMessage *msg) + SoupMessage * msg) { GError *error = NULL; @@ -730,9 +730,10 @@ connect_to_websocket_server_async (void) SoupLogger *logger; SoupMessage *message; SoupSession *session; - const char *https_aliases[] = {"wss", NULL}; + const char *https_aliases[] = { "wss", NULL }; - session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl, + session = + soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, //SOUP_SESSION_SSL_CA_FILE, "/etc/ssl/certs/ca-bundle.crt", SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); @@ -759,7 +760,8 @@ check_plugins (void) GstPlugin *plugin; GstRegistry *registry; const gchar *needed[] = { "opus", "vpx", "nice", "webrtc", "dtls", "srtp", - "rtpmanager", "videotestsrc", "audiotestsrc", NULL}; + "rtpmanager", "videotestsrc", "audiotestsrc", NULL + }; registry = gst_registry_get (); ret = TRUE; From 133a1593ee219b60173b367a42a5d814eef419c5 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2020 20:13:56 +0300 Subject: [PATCH 336/412] android, sendrecv: add missing break in switch case statements --- webrtc/android/app/src/main/jni/webrtc.c | 1 + webrtc/sendrecv/gst/webrtc-sendrecv.c | 1 + 2 files changed, 2 insertions(+) diff --git a/webrtc/android/app/src/main/jni/webrtc.c b/webrtc/android/app/src/main/jni/webrtc.c index badd485cb9..bfcd8b24b2 100644 --- a/webrtc/android/app/src/main/jni/webrtc.c +++ b/webrtc/android/app/src/main/jni/webrtc.c @@ -498,6 +498,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, case PEER_CONNECTED: case PEER_CALL_NEGOTIATING: webrtc->app_state = PEER_CALL_ERROR; + break; default: webrtc->app_state = APP_STATE_ERROR; } diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 584863d0c4..f2fef123cc 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -602,6 +602,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, case PEER_CONNECTED: case PEER_CALL_NEGOTIATING: app_state = PEER_CALL_ERROR; + break; default: app_state = APP_STATE_ERROR; } From 8c4345da7d86686974e37b0cecd753fb6e20f3ac Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2020 20:13:37 +0300 Subject: [PATCH 337/412] android, mp-webrtc-sendrecv, sendonly: cleanup webrtc-unidirectional-h264.c: removed empty lines android: removed unused var --- webrtc/android/app/src/main/jni/webrtc.c | 1 - webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c | 3 +-- webrtc/sendonly/webrtc-unidirectional-h264.c | 12 ------------ 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/webrtc/android/app/src/main/jni/webrtc.c b/webrtc/android/app/src/main/jni/webrtc.c index bfcd8b24b2..6bfd16a556 100644 --- a/webrtc/android/app/src/main/jni/webrtc.c +++ b/webrtc/android/app/src/main/jni/webrtc.c @@ -280,7 +280,6 @@ on_offer_created (GstPromise * promise, WebRTC * webrtc) { GstWebRTCSessionDescription *offer = NULL; const GstStructure *reply; - gchar *desc; g_assert (webrtc->app_state == PEER_CALL_NEGOTIATING); diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 6e66fbd162..1c5b0ecc23 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -404,7 +404,6 @@ incoming_call_from_peer (const gchar * peer_id) #define STR(x) #x #define RTP_CAPS_OPUS(x) "application/x-rtp,media=audio,encoding-name=OPUS,payload=" STR(x) -#define RTP_CAPS_VP8(x) "application/x-rtp,media=video,encoding-name=VP8,payload=" STR(x) static gboolean start_pipeline (void) @@ -902,7 +901,6 @@ check_plugins (void) { int i; gboolean ret; - GstPlugin *plugin; GstRegistry *registry; const gchar *needed[] = { "opus", "nice", "webrtc", "dtls", "srtp", "rtpmanager", "audiotestsrc", NULL @@ -911,6 +909,7 @@ check_plugins (void) registry = gst_registry_get (); ret = TRUE; for (i = 0; i < g_strv_length ((gchar **) needed); i++) { + GstPlugin *plugin; plugin = gst_registry_find_plugin (registry, needed[i]); if (!plugin) { g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index e6f7a59ae0..bb9ca8e44b 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -11,14 +11,10 @@ #include #include - - #define RTP_PAYLOAD_TYPE "96" #define SOUP_HTTP_PORT 57778 #define STUN_SERVER "stun.l.google.com:19302" - - typedef struct _ReceiverEntry ReceiverEntry; ReceiverEntry *create_receiver_entry (SoupWebsocketConnection * connection); @@ -48,9 +44,6 @@ static gchar *get_string_from_json_object (JsonObject * object); gboolean exit_sighandler (gpointer user_data); - - - struct _ReceiverEntry { SoupWebsocketConnection *connection; @@ -59,8 +52,6 @@ struct _ReceiverEntry GstElement *webrtcbin; }; - - const gchar *html_source = " \n \ \n \ \n \ @@ -166,9 +157,6 @@ const gchar *html_source = " \n \ \n \ "; - - - ReceiverEntry * create_receiver_entry (SoupWebsocketConnection * connection) { From 56a03add7873298399985c72c45daece18154855 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Wed, 15 Apr 2020 11:08:40 +0300 Subject: [PATCH 338/412] html: charset Avoid warning: The character encoding of the HTML document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol. --- webrtc/sendrecv/js/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html index cdd3151a7c..8325070e8a 100644 --- a/webrtc/sendrecv/js/index.html +++ b/webrtc/sendrecv/js/index.html @@ -11,6 +11,7 @@ --> + From 37cf0dffb54be3a7ba319fb17a49aa265c5e92b5 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Thu, 12 Sep 2019 19:15:49 +1000 Subject: [PATCH 339/412] add __pycache__ to .gitignore --- webrtc/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/webrtc/.gitignore b/webrtc/.gitignore index 2747cceb90..99215a08a7 100644 --- a/webrtc/.gitignore +++ b/webrtc/.gitignore @@ -52,3 +52,4 @@ out/ # Our stuff *.pem webrtc-sendrecv +__pycache__ From bc821a85d4b418d2c4ff670f76e203fc0bfc3290 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Mon, 10 Sep 2018 18:08:15 +1000 Subject: [PATCH 340/412] tests: first pass at some basic browser tests --- webrtc/meson.build | 11 + webrtc/sendrecv/gst/meson.build | 4 + webrtc/sendrecv/gst/tests/basic.py | 144 +++++++++ webrtc/sendrecv/gst/tests/meson.build | 15 + ...{webrtc-sendrecv.py => webrtc_sendrecv.py} | 28 +- webrtc/signalling/generate_cert.sh | 11 +- webrtc/signalling/meson.build | 8 + webrtc/signalling/simple-server.py | 286 ----------------- webrtc/signalling/simple_server.py | 291 ++++++++++++++++++ 9 files changed, 505 insertions(+), 293 deletions(-) create mode 100644 webrtc/sendrecv/gst/tests/basic.py create mode 100644 webrtc/sendrecv/gst/tests/meson.build rename webrtc/sendrecv/gst/{webrtc-sendrecv.py => webrtc_sendrecv.py} (90%) create mode 100644 webrtc/signalling/meson.build delete mode 100755 webrtc/signalling/simple-server.py create mode 100755 webrtc/signalling/simple_server.py diff --git a/webrtc/meson.build b/webrtc/meson.build index 482b82498a..3ee35f7bd9 100644 --- a/webrtc/meson.build +++ b/webrtc/meson.build @@ -1,5 +1,6 @@ project('gstwebrtc-demo', 'c', meson_version : '>= 0.48', + license: 'BSD-2-Clause', default_options : [ 'warning_level=1', 'buildtype=debug' ]) @@ -24,5 +25,15 @@ libsoup_dep = dependency('libsoup-2.4', version : '>=2.48', json_glib_dep = dependency('json-glib-1.0', fallback : ['json-glib', 'json_glib_dep']) + +py3_mod = import('python3') +py3 = py3_mod.find_python() + +py3_version = py3_mod.language_version() +if py3_version.version_compare('< 3.6') + error('Could not find a sufficient python version required: 3.6, found {}'.format(py3_version)) +endif + subdir('multiparty-sendrecv') +subdir('signalling') subdir('sendrecv') diff --git a/webrtc/sendrecv/gst/meson.build b/webrtc/sendrecv/gst/meson.build index 7150e42b0c..85950add4a 100644 --- a/webrtc/sendrecv/gst/meson.build +++ b/webrtc/sendrecv/gst/meson.build @@ -1,3 +1,7 @@ executable('webrtc-sendrecv', 'webrtc-sendrecv.c', dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, libsoup_dep, json_glib_dep ]) + +webrtc_py = files('webrtc_sendrecv.py') + +subdir('tests') diff --git a/webrtc/sendrecv/gst/tests/basic.py b/webrtc/sendrecv/gst/tests/basic.py new file mode 100644 index 0000000000..84ce81de76 --- /dev/null +++ b/webrtc/sendrecv/gst/tests/basic.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +import os +import unittest +from selenium import webdriver +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from selenium.webdriver.chrome.options import Options as COptions +import webrtc_sendrecv as webrtc +import simple_server as sserver +import asyncio +import threading +import signal + +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst + +thread = None +stop = None +server = None + +class AsyncIOThread(threading.Thread): + def __init__ (self, loop): + threading.Thread.__init__(self) + self.loop = loop + + def run(self): + asyncio.set_event_loop(self.loop) + self.loop.run_forever() + self.loop.close() + print ("closed loop") + + def stop_thread(self): + self.loop.call_soon_threadsafe(self.loop.stop) + +async def run_until(server, stop_token): + async with server: + await stop_token + print ("run_until done") + +def setUpModule(): + global thread, server + Gst.init(None) + cacerts_path = os.environ.get('TEST_CA_CERT_PATH') + loop = asyncio.new_event_loop() + + thread = AsyncIOThread(loop) + thread.start() + server = sserver.WebRTCSimpleServer('127.0.0.1', 8443, 20, False, cacerts_path) + def f(): + global stop + stop = asyncio.ensure_future(server.run()) + loop.call_soon_threadsafe(f) + +def tearDownModule(): + global thread, stop + stop.cancel() + thread.stop_thread() + thread.join() + print("thread joined") + +def valid_int(n): + if isinstance(n, int): + return True + if isinstance(n, str): + try: + num = int(n) + return True + except: + return False + return False + +def create_firefox_driver(): + capabilities = webdriver.DesiredCapabilities().FIREFOX.copy() + capabilities['acceptSslCerts'] = True + capabilities['acceptInsecureCerts'] = True + profile = FirefoxProfile() + profile.set_preference ('media.navigator.streams.fake', True) + profile.set_preference ('media.navigator.permission.disabled', True) + + return webdriver.Firefox(firefox_profile=profile, capabilities=capabilities) + +def create_chrome_driver(): + capabilities = webdriver.DesiredCapabilities().CHROME.copy() + capabilities['acceptSslCerts'] = True + capabilities['acceptInsecureCerts'] = True + copts = COptions() + copts.add_argument('--allow-file-access-from-files') + copts.add_argument('--use-fake-ui-for-media-stream') + copts.add_argument('--use-fake-device-for-media-stream') + copts.add_argument('--enable-blink-features=RTCUnifiedPlanByDefault') + + return webdriver.Chrome(options=copts, desired_capabilities=capabilities) + +class ServerConnectionTestCase(unittest.TestCase): + def setUp(self): + self.browser = create_firefox_driver() +# self.browser = create_chrome_driver() + self.addCleanup(self.browser.quit) + self.html_source = os.environ.get('TEST_HTML_SOURCE') + self.assertIsNot(self.html_source, None) + self.assertNotEqual(self.html_source, '') + self.html_source = 'file://' + self.html_source + '/index.html' + + def get_peer_id(self): + self.browser.get(self.html_source) + peer_id = WebDriverWait(self.browser, 5).until( + lambda x: x.find_element_by_id('peer-id'), + message='Peer-id element was never seen' + ) + WebDriverWait (self.browser, 5).until( + lambda x: valid_int(peer_id.text), + message='Peer-id never became a number' + ) + return int(peer_id.text) + + def testPeerID(self): + self.get_peer_id() + + def testPerformCall(self): + loop = asyncio.new_event_loop() + thread = AsyncIOThread(loop) + thread.start() + peer_id = self.get_peer_id() + client = webrtc.WebRTCClient(peer_id + 1, peer_id, 'wss://127.0.0.1:8443') + + async def do_things(): + await client.connect() + async def stop_after(client, delay): + await asyncio.sleep(delay) + await client.stop() + future = asyncio.ensure_future (stop_after (client, 5)) + res = await client.loop() + thread.stop_thread() + return res + + res = asyncio.run_coroutine_threadsafe(do_things(), loop).result() + thread.join() + print ("client thread joined") + self.assertEqual(res, 0) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/webrtc/sendrecv/gst/tests/meson.build b/webrtc/sendrecv/gst/tests/meson.build new file mode 100644 index 0000000000..dfca6ebae7 --- /dev/null +++ b/webrtc/sendrecv/gst/tests/meson.build @@ -0,0 +1,15 @@ +tests = [ + ['basic', 'basic.py'], +] + +test_deps = [certs] + +foreach elem : tests + test(elem.get(0), + py3, + depends: test_deps, + args : files(elem.get(1)), + env : ['PYTHONPATH=' + join_paths(meson.source_root(), 'sendrecv', 'gst') + ':' + join_paths(meson.source_root(), 'signalling'), + 'TEST_HTML_SOURCE=' + join_paths(meson.source_root(), 'sendrecv', 'js'), + 'TEST_CA_CERT_PATH=' + join_paths(meson.build_root(), 'signalling')]) +endforeach diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.py b/webrtc/sendrecv/gst/webrtc_sendrecv.py similarity index 90% rename from webrtc/sendrecv/gst/webrtc-sendrecv.py rename to webrtc/sendrecv/gst/webrtc_sendrecv.py index b19bc3fa6f..b101e8cab9 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.py +++ b/webrtc/sendrecv/gst/webrtc_sendrecv.py @@ -23,6 +23,8 @@ webrtcbin name=sendrecv bundle-policy=max-bundle stun-server=stun://stun.l.googl queue ! application/x-rtp,media=audio,encoding-name=OPUS,payload=96 ! sendrecv. ''' +from websockets.version import version as wsv + class WebRTCClient: def __init__(self, id_, peer_id, server): self.id_ = id_ @@ -32,10 +34,11 @@ class WebRTCClient: self.peer_id = peer_id self.server = server or 'wss://webrtc.nirbheek.in:8443' + async def connect(self): sslctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) self.conn = await websockets.connect(self.server, ssl=sslctx) - await self.conn.send('HELLO %d' % our_id) + await self.conn.send('HELLO %d' % self.id_) async def setup_call(self): await self.conn.send('SESSION {}'.format(self.peer_id)) @@ -46,6 +49,7 @@ class WebRTCClient: msg = json.dumps({'sdp': {'type': 'offer', 'sdp': text}}) loop = asyncio.new_event_loop() loop.run_until_complete(self.conn.send(msg)) + loop.close() def on_offer_created(self, promise, _, __): promise.wait() @@ -64,6 +68,7 @@ class WebRTCClient: icemsg = json.dumps({'ice': {'candidate': candidate, 'sdpMLineIndex': mlineindex}}) loop = asyncio.new_event_loop() loop.run_until_complete(self.conn.send(icemsg)) + loop.close() def on_incoming_decodebin_stream(self, _, pad): if not pad.has_current_caps(): @@ -113,7 +118,7 @@ class WebRTCClient: self.webrtc.connect('pad-added', self.on_incoming_stream) self.pipe.set_state(Gst.State.PLAYING) - async def handle_sdp(self, message): + def handle_sdp(self, message): assert (self.webrtc) msg = json.loads(message) if 'sdp' in msg: @@ -133,6 +138,11 @@ class WebRTCClient: sdpmlineindex = ice['sdpMLineIndex'] self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) + def close_pipeline(self): + self.pipe.set_state(Gst.State.NULL) + self.pipe = None + self.webrtc = None + async def loop(self): assert self.conn async for message in self.conn: @@ -142,11 +152,18 @@ class WebRTCClient: self.start_pipeline() elif message.startswith('ERROR'): print (message) + self.close_pipeline() return 1 else: - await self.handle_sdp(message) + self.handle_sdp(message) + self.close_pipeline() return 0 + async def stop(self): + if self.conn: + await self.conn.close() + self.conn = None + def check_plugins(): needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", @@ -168,6 +185,7 @@ if __name__=='__main__': args = parser.parse_args() our_id = random.randrange(10, 10000) c = WebRTCClient(our_id, args.peerid, args.server) - asyncio.get_event_loop().run_until_complete(c.connect()) - res = asyncio.get_event_loop().run_until_complete(c.loop()) + loop = asyncio.get_event_loop() + loop.run_until_complete(c.connect()) + res = loop.run_until_complete(c.loop()) sys.exit(res) diff --git a/webrtc/signalling/generate_cert.sh b/webrtc/signalling/generate_cert.sh index 68a4b96f3c..7f4084fffa 100755 --- a/webrtc/signalling/generate_cert.sh +++ b/webrtc/signalling/generate_cert.sh @@ -1,3 +1,10 @@ -#! /bin/bash +#! /bin/sh -openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +BASE_DIR=$(dirname $0) + +OUTDIR="" +if [ $# -eq 1 ]; then + OUTDIR=$1/ +fi + +openssl req -x509 -newkey rsa:4096 -keyout ${OUTDIR}key.pem -out ${OUTDIR}cert.pem -days 365 -nodes -subj "/CN=example.com" diff --git a/webrtc/signalling/meson.build b/webrtc/signalling/meson.build new file mode 100644 index 0000000000..43a53bfe73 --- /dev/null +++ b/webrtc/signalling/meson.build @@ -0,0 +1,8 @@ +generate_certs = find_program('generate_cert.sh') +certs = custom_target( + 'generate-certs', + command: [generate_certs, '@OUTDIR@'], + output : ['key.pem', 'cert.pem'] +) + +simple_server = files('simple_server.py') diff --git a/webrtc/signalling/simple-server.py b/webrtc/signalling/simple-server.py deleted file mode 100755 index b337eae2bc..0000000000 --- a/webrtc/signalling/simple-server.py +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python3 -# -# Example 1-1 call signalling server -# -# Copyright (C) 2017 Centricular Ltd. -# -# Author: Nirbheek Chauhan -# - -import os -import sys -import ssl -import logging -import asyncio -import websockets -import argparse -import http - -from concurrent.futures._base import TimeoutError - -parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) -# See: host, port in https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.create_server -parser.add_argument('--addr', default='', help='Address to listen on (default: all interfaces, both ipv4 and ipv6)') -parser.add_argument('--port', default=8443, type=int, help='Port to listen on') -parser.add_argument('--keepalive-timeout', dest='keepalive_timeout', default=30, type=int, help='Timeout for keepalive (in seconds)') -parser.add_argument('--cert-path', default=os.path.dirname(__file__)) -parser.add_argument('--disable-ssl', default=False, help='Disable ssl', action='store_true') -parser.add_argument('--health', default='/health', help='Health check route') - -options = parser.parse_args(sys.argv[1:]) - -ADDR_PORT = (options.addr, options.port) -KEEPALIVE_TIMEOUT = options.keepalive_timeout - -############### Global data ############### - -# Format: {uid: (Peer WebSocketServerProtocol, -# remote_address, -# <'session'|room_id|None>)} -peers = dict() -# Format: {caller_uid: callee_uid, -# callee_uid: caller_uid} -# Bidirectional mapping between the two peers -sessions = dict() -# Format: {room_id: {peer1_id, peer2_id, peer3_id, ...}} -# Room dict with a set of peers in each room -rooms = dict() - -############### Helper functions ############### - -async def health_check(path, request_headers): - if path == options.health: - return http.HTTPStatus.OK, [], b"OK\n" - -async def recv_msg_ping(ws, raddr): - ''' - Wait for a message forever, and send a regular ping to prevent bad routers - from closing the connection. - ''' - msg = None - while msg is None: - try: - msg = await asyncio.wait_for(ws.recv(), KEEPALIVE_TIMEOUT) - except TimeoutError: - print('Sending keepalive ping to {!r} in recv'.format(raddr)) - await ws.ping() - return msg - -async def disconnect(ws, peer_id): - ''' - Remove @peer_id from the list of sessions and close our connection to it. - This informs the peer that the session and all calls have ended, and it - must reconnect. - ''' - global sessions - if peer_id in sessions: - del sessions[peer_id] - # Close connection - if ws and ws.open: - # Don't care about errors - asyncio.ensure_future(ws.close(reason='hangup')) - -async def cleanup_session(uid): - if uid in sessions: - other_id = sessions[uid] - del sessions[uid] - print("Cleaned up {} session".format(uid)) - if other_id in sessions: - del sessions[other_id] - print("Also cleaned up {} session".format(other_id)) - # If there was a session with this peer, also - # close the connection to reset its state. - if other_id in peers: - print("Closing connection to {}".format(other_id)) - wso, oaddr, _ = peers[other_id] - del peers[other_id] - await wso.close() - -async def cleanup_room(uid, room_id): - room_peers = rooms[room_id] - if uid not in room_peers: - return - room_peers.remove(uid) - for pid in room_peers: - wsp, paddr, _ = peers[pid] - msg = 'ROOM_PEER_LEFT {}'.format(uid) - print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) - await wsp.send(msg) - -async def remove_peer(uid): - await cleanup_session(uid) - if uid in peers: - ws, raddr, status = peers[uid] - if status and status != 'session': - await cleanup_room(uid, status) - del peers[uid] - await ws.close() - print("Disconnected from peer {!r} at {!r}".format(uid, raddr)) - -############### Handler functions ############### - -async def connection_handler(ws, uid): - global peers, sessions, rooms - raddr = ws.remote_address - peer_status = None - peers[uid] = [ws, raddr, peer_status] - print("Registered peer {!r} at {!r}".format(uid, raddr)) - while True: - # Receive command, wait forever if necessary - msg = await recv_msg_ping(ws, raddr) - # Update current status - peer_status = peers[uid][2] - # We are in a session or a room, messages must be relayed - if peer_status is not None: - # We're in a session, route message to connected peer - if peer_status == 'session': - other_id = sessions[uid] - wso, oaddr, status = peers[other_id] - assert(status == 'session') - print("{} -> {}: {}".format(uid, other_id, msg)) - await wso.send(msg) - # We're in a room, accept room-specific commands - elif peer_status: - # ROOM_PEER_MSG peer_id MSG - if msg.startswith('ROOM_PEER_MSG'): - _, other_id, msg = msg.split(maxsplit=2) - if other_id not in peers: - await ws.send('ERROR peer {!r} not found' - ''.format(other_id)) - continue - wso, oaddr, status = peers[other_id] - if status != room_id: - await ws.send('ERROR peer {!r} is not in the room' - ''.format(other_id)) - continue - msg = 'ROOM_PEER_MSG {} {}'.format(uid, msg) - print('room {}: {} -> {}: {}'.format(room_id, uid, other_id, msg)) - await wso.send(msg) - elif msg == 'ROOM_PEER_LIST': - room_id = peers[peer_id][2] - room_peers = ' '.join([pid for pid in rooms[room_id] if pid != peer_id]) - msg = 'ROOM_PEER_LIST {}'.format(room_peers) - print('room {}: -> {}: {}'.format(room_id, uid, msg)) - await ws.send(msg) - else: - await ws.send('ERROR invalid msg, already in room') - continue - else: - raise AssertionError('Unknown peer status {!r}'.format(peer_status)) - # Requested a session with a specific peer - elif msg.startswith('SESSION'): - print("{!r} command {!r}".format(uid, msg)) - _, callee_id = msg.split(maxsplit=1) - if callee_id not in peers: - await ws.send('ERROR peer {!r} not found'.format(callee_id)) - continue - if peer_status is not None: - await ws.send('ERROR peer {!r} busy'.format(callee_id)) - continue - await ws.send('SESSION_OK') - wsc = peers[callee_id][0] - print('Session from {!r} ({!r}) to {!r} ({!r})' - ''.format(uid, raddr, callee_id, wsc.remote_address)) - # Register session - peers[uid][2] = peer_status = 'session' - sessions[uid] = callee_id - peers[callee_id][2] = 'session' - sessions[callee_id] = uid - # Requested joining or creation of a room - elif msg.startswith('ROOM'): - print('{!r} command {!r}'.format(uid, msg)) - _, room_id = msg.split(maxsplit=1) - # Room name cannot be 'session', empty, or contain whitespace - if room_id == 'session' or room_id.split() != [room_id]: - await ws.send('ERROR invalid room id {!r}'.format(room_id)) - continue - if room_id in rooms: - if uid in rooms[room_id]: - raise AssertionError('How did we accept a ROOM command ' - 'despite already being in a room?') - else: - # Create room if required - rooms[room_id] = set() - room_peers = ' '.join([pid for pid in rooms[room_id]]) - await ws.send('ROOM_OK {}'.format(room_peers)) - # Enter room - peers[uid][2] = peer_status = room_id - rooms[room_id].add(uid) - for pid in rooms[room_id]: - if pid == uid: - continue - wsp, paddr, _ = peers[pid] - msg = 'ROOM_PEER_JOINED {}'.format(uid) - print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) - await wsp.send(msg) - else: - print('Ignoring unknown message {!r} from {!r}'.format(msg, uid)) - -async def hello_peer(ws): - ''' - Exchange hello, register peer - ''' - raddr = ws.remote_address - hello = await ws.recv() - hello, uid = hello.split(maxsplit=1) - if hello != 'HELLO': - await ws.close(code=1002, reason='invalid protocol') - raise Exception("Invalid hello from {!r}".format(raddr)) - if not uid or uid in peers or uid.split() != [uid]: # no whitespace - await ws.close(code=1002, reason='invalid peer uid') - raise Exception("Invalid uid {!r} from {!r}".format(uid, raddr)) - # Send back a HELLO - await ws.send('HELLO') - return uid - -async def handler(ws, path): - ''' - All incoming messages are handled here. @path is unused. - ''' - raddr = ws.remote_address - print("Connected to {!r}".format(raddr)) - peer_id = await hello_peer(ws) - try: - await connection_handler(ws, peer_id) - except websockets.ConnectionClosed: - print("Connection to peer {!r} closed, exiting handler".format(raddr)) - finally: - await remove_peer(peer_id) - -sslctx = None -if not options.disable_ssl: - # Create an SSL context to be used by the websocket server - certpath = options.cert_path - print('Using TLS with keys in {!r}'.format(certpath)) - if 'letsencrypt' in certpath: - chain_pem = os.path.join(certpath, 'fullchain.pem') - key_pem = os.path.join(certpath, 'privkey.pem') - else: - chain_pem = os.path.join(certpath, 'cert.pem') - key_pem = os.path.join(certpath, 'key.pem') - - sslctx = ssl.create_default_context() - try: - sslctx.load_cert_chain(chain_pem, keyfile=key_pem) - except FileNotFoundError: - print("Certificates not found, did you run generate_cert.sh?") - sys.exit(1) - # FIXME - sslctx.check_hostname = False - sslctx.verify_mode = ssl.CERT_NONE - -print("Listening on https://{}:{}".format(*ADDR_PORT)) -# Websocket server -wsd = websockets.serve(handler, *ADDR_PORT, ssl=sslctx, process_request=health_check, - # Maximum number of messages that websockets will pop - # off the asyncio and OS buffers per connection. See: - # https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol - max_queue=16) - -logger = logging.getLogger('websockets.server') - -logger.setLevel(logging.ERROR) -logger.addHandler(logging.StreamHandler()) - -asyncio.get_event_loop().run_until_complete(wsd) -asyncio.get_event_loop().run_forever() diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py new file mode 100755 index 0000000000..ead3034a36 --- /dev/null +++ b/webrtc/signalling/simple_server.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +# +# Example 1-1 call signalling server +# +# Copyright (C) 2017 Centricular Ltd. +# +# Author: Nirbheek Chauhan +# + +import os +import sys +import ssl +import logging +import asyncio +import websockets +import argparse +import http + +from concurrent.futures._base import TimeoutError + +class WebRTCSimpleServer(object): + + def __init__(self, addr, port, keepalive_timeout, disable_ssl, certpath, health_path=None): + ############### Global data ############### + + # Format: {uid: (Peer WebSocketServerProtocol, + # remote_address, + # <'session'|room_id|None>)} + self.peers = dict() + # Format: {caller_uid: callee_uid, + # callee_uid: caller_uid} + # Bidirectional mapping between the two peers + self.sessions = dict() + # Format: {room_id: {peer1_id, peer2_id, peer3_id, ...}} + # Room dict with a set of peers in each room + self.rooms = dict() + + self.keepalive_timeout = keepalive_timeout + self.addr = addr + self.port = port + self.disable_ssl = disable_ssl + self.certpath = certpath + self.health_path = health_path + + ############### Helper functions ############### + + async def health_check(self, path, request_headers): + if path == self.health_part: + return http.HTTPStatus.OK, [], b"OK\n" + return None + + async def recv_msg_ping(self, ws, raddr): + ''' + Wait for a message forever, and send a regular ping to prevent bad routers + from closing the connection. + ''' + msg = None + while msg is None: + try: + msg = await asyncio.wait_for(ws.recv(), self.keepalive_timeout) + except TimeoutError: + print('Sending keepalive ping to {!r} in recv'.format(raddr)) + await ws.ping() + return msg + + async def cleanup_session(self, uid): + if uid in self.sessions: + other_id = self.sessions[uid] + del self.sessions[uid] + print("Cleaned up {} session".format(uid)) + if other_id in self.sessions: + del self.sessions[other_id] + print("Also cleaned up {} session".format(other_id)) + # If there was a session with this peer, also + # close the connection to reset its state. + if other_id in self.peers: + print("Closing connection to {}".format(other_id)) + wso, oaddr, _ = self.peers[other_id] + del self.peers[other_id] + await wso.close() + + async def cleanup_room(self, uid, room_id): + room_peers = self.rooms[room_id] + if uid not in room_peers: + return + room_peers.remove(uid) + for pid in room_peers: + wsp, paddr, _ = self.peers[pid] + msg = 'ROOM_PEER_LEFT {}'.format(uid) + print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) + await wsp.send(msg) + + async def remove_peer(self, uid): + await self.cleanup_session(uid) + if uid in self.peers: + ws, raddr, status = self.peers[uid] + if status and status != 'session': + await self.cleanup_room(uid, status) + del self.peers[uid] + await ws.close() + print("Disconnected from peer {!r} at {!r}".format(uid, raddr)) + + ############### Handler functions ############### + + + async def connection_handler(self, ws, uid): + raddr = ws.remote_address + peer_status = None + self.peers[uid] = [ws, raddr, peer_status] + print("Registered peer {!r} at {!r}".format(uid, raddr)) + while True: + # Receive command, wait forever if necessary + msg = await self.recv_msg_ping(ws, raddr) + # Update current status + peer_status = self.peers[uid][2] + # We are in a session or a room, messages must be relayed + if peer_status is not None: + # We're in a session, route message to connected peer + if peer_status == 'session': + other_id = self.sessions[uid] + wso, oaddr, status = self.peers[other_id] + assert(status == 'session') + print("{} -> {}: {}".format(uid, other_id, msg)) + await wso.send(msg) + # We're in a room, accept room-specific commands + elif peer_status: + # ROOM_PEER_MSG peer_id MSG + if msg.startswith('ROOM_PEER_MSG'): + _, other_id, msg = msg.split(maxsplit=2) + if other_id not in self.peers: + await ws.send('ERROR peer {!r} not found' + ''.format(other_id)) + continue + wso, oaddr, status = self.peers[other_id] + if status != room_id: + await ws.send('ERROR peer {!r} is not in the room' + ''.format(other_id)) + continue + msg = 'ROOM_PEER_MSG {} {}'.format(uid, msg) + print('room {}: {} -> {}: {}'.format(room_id, uid, other_id, msg)) + await wso.send(msg) + elif msg == 'ROOM_PEER_LIST': + room_id = self.peers[peer_id][2] + room_peers = ' '.join([pid for pid in self.rooms[room_id] if pid != peer_id]) + msg = 'ROOM_PEER_LIST {}'.format(room_peers) + print('room {}: -> {}: {}'.format(room_id, uid, msg)) + await ws.send(msg) + else: + await ws.send('ERROR invalid msg, already in room') + continue + else: + raise AssertionError('Unknown peer status {!r}'.format(peer_status)) + # Requested a session with a specific peer + elif msg.startswith('SESSION'): + print("{!r} command {!r}".format(uid, msg)) + _, callee_id = msg.split(maxsplit=1) + if callee_id not in self.peers: + await ws.send('ERROR peer {!r} not found'.format(callee_id)) + continue + if peer_status is not None: + await ws.send('ERROR peer {!r} busy'.format(callee_id)) + continue + await ws.send('SESSION_OK') + wsc = self.peers[callee_id][0] + print('Session from {!r} ({!r}) to {!r} ({!r})' + ''.format(uid, raddr, callee_id, wsc.remote_address)) + # Register session + self.peers[uid][2] = peer_status = 'session' + self.sessions[uid] = callee_id + self.peers[callee_id][2] = 'session' + self.sessions[callee_id] = uid + # Requested joining or creation of a room + elif msg.startswith('ROOM'): + print('{!r} command {!r}'.format(uid, msg)) + _, room_id = msg.split(maxsplit=1) + # Room name cannot be 'session', empty, or contain whitespace + if room_id == 'session' or room_id.split() != [room_id]: + await ws.send('ERROR invalid room id {!r}'.format(room_id)) + continue + if room_id in self.rooms: + if uid in self.rooms[room_id]: + raise AssertionError('How did we accept a ROOM command ' + 'despite already being in a room?') + else: + # Create room if required + self.rooms[room_id] = set() + room_peers = ' '.join([pid for pid in self.rooms[room_id]]) + await ws.send('ROOM_OK {}'.format(room_peers)) + # Enter room + self.peers[uid][2] = peer_status = room_id + self.rooms[room_id].add(uid) + for pid in self.rooms[room_id]: + if pid == uid: + continue + wsp, paddr, _ = self.peers[pid] + msg = 'ROOM_PEER_JOINED {}'.format(uid) + print('room {}: {} -> {}: {}'.format(room_id, uid, pid, msg)) + await wsp.send(msg) + else: + print('Ignoring unknown message {!r} from {!r}'.format(msg, uid)) + + async def hello_peer(self, ws): + ''' + Exchange hello, register peer + ''' + raddr = ws.remote_address + hello = await ws.recv() + hello, uid = hello.split(maxsplit=1) + if hello != 'HELLO': + await ws.close(code=1002, reason='invalid protocol') + raise Exception("Invalid hello from {!r}".format(raddr)) + if not uid or uid in self.peers or uid.split() != [uid]: # no whitespace + await ws.close(code=1002, reason='invalid peer uid') + raise Exception("Invalid uid {!r} from {!r}".format(uid, raddr)) + # Send back a HELLO + await ws.send('HELLO') + return uid + + def run(self): + sslctx = None + if not self.disable_ssl: + # Create an SSL context to be used by the websocket server + print('Using TLS with keys in {!r}'.format(self.certpath)) + if 'letsencrypt' in self.certpath: + chain_pem = os.path.join(self.certpath, 'fullchain.pem') + key_pem = os.path.join(self.certpath, 'privkey.pem') + else: + chain_pem = os.path.join(self.certpath, 'cert.pem') + key_pem = os.path.join(self.certpath, 'key.pem') + + sslctx = ssl.create_default_context() + try: + sslctx.load_cert_chain(chain_pem, keyfile=key_pem) + except FileNotFoundError: + print("Certificates not found, did you run generate_cert.sh?") + sys.exit(1) + # FIXME + sslctx.check_hostname = False + sslctx.verify_mode = ssl.CERT_NONE + + async def handler(ws, path): + ''' + All incoming messages are handled here. @path is unused. + ''' + raddr = ws.remote_address + print("Connected to {!r}".format(raddr)) + peer_id = await self.hello_peer(ws) + try: + await self.connection_handler(ws, peer_id) + except websockets.ConnectionClosed: + print("Connection to peer {!r} closed, exiting handler".format(raddr)) + finally: + await self.remove_peer(peer_id) + + print("Listening on https://{}:{}".format(self.addr, self.port)) + # Websocket server + wsd = websockets.serve(handler, self.addr, self.port, ssl=sslctx, process_request=self.health_check if self.health_path else None, + # Maximum number of messages that websockets will pop + # off the asyncio and OS buffers per connection. See: + # https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol + max_queue=16) + + logger = logging.getLogger('websockets.server') + + logger.setLevel(logging.ERROR) + logger.addHandler(logging.StreamHandler()) + + return wsd + +def main(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + # See: host, port in https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.create_server + parser.add_argument('--addr', default='', help='Address to listen on (default: all interfaces, both ipv4 and ipv6)') + parser.add_argument('--port', default=8443, type=int, help='Port to listen on') + parser.add_argument('--keepalive-timeout', dest='keepalive_timeout', default=30, type=int, help='Timeout for keepalive (in seconds)') + parser.add_argument('--cert-path', default=os.path.dirname(__file__)) + parser.add_argument('--disable-ssl', default=False, help='Disable ssl', action='store_true') + parser.add_argument('--health', default='/health', help='Health check route') + + options = parser.parse_args(sys.argv[1:]) + + loop = asyncio.get_event_loop() + + r = WebRTCSimpleServer(options.addr, options.port, options.keepalive_timeout, options.disable_ssl, options.cert_path) + + loop.run_until_complete (r.run()) + loop.run_forever () + print ("Goodbye!") + +if __name__ == "__main__": + main() From c3f629340d3ba6ad2b4b1db863a76b4a8c98ff61 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Mon, 17 Dec 2018 22:34:10 +1100 Subject: [PATCH 341/412] check: first pass at a couple of validate tests --- webrtc/{sendrecv/gst/tests => check}/basic.py | 0 .../{sendrecv/gst/tests => check}/meson.build | 0 webrtc/check/validate/README.md | 17 + webrtc/check/validate/actions.py | 77 +++++ webrtc/check/validate/apps/__init__.py | 0 webrtc/check/validate/apps/gstwebrtc.py | 187 ++++++++++ webrtc/check/validate/browser.py | 79 +++++ webrtc/check/validate/client.py | 212 ++++++++++++ webrtc/check/validate/enums.py | 36 ++ webrtc/check/validate/observer.py | 43 +++ .../validate/scenarios/offer_answer.scenario | 3 + .../scenarios/vp8_send_stream.scenario | 5 + webrtc/check/validate/signalling.py | 161 +++++++++ webrtc/check/validate/testsuites/__init__.py | 0 webrtc/check/validate/testsuites/webrtc.py | 36 ++ webrtc/check/validate/web/single_stream.html | 35 ++ webrtc/check/validate/web/webrtc.js | 320 ++++++++++++++++++ webrtc/check/validate/webrtc_validate.py | 203 +++++++++++ webrtc/meson.build | 2 + webrtc/sendrecv/gst/meson.build | 2 - 20 files changed, 1416 insertions(+), 2 deletions(-) rename webrtc/{sendrecv/gst/tests => check}/basic.py (100%) rename webrtc/{sendrecv/gst/tests => check}/meson.build (100%) create mode 100644 webrtc/check/validate/README.md create mode 100644 webrtc/check/validate/actions.py create mode 100644 webrtc/check/validate/apps/__init__.py create mode 100644 webrtc/check/validate/apps/gstwebrtc.py create mode 100644 webrtc/check/validate/browser.py create mode 100644 webrtc/check/validate/client.py create mode 100644 webrtc/check/validate/enums.py create mode 100644 webrtc/check/validate/observer.py create mode 100644 webrtc/check/validate/scenarios/offer_answer.scenario create mode 100644 webrtc/check/validate/scenarios/vp8_send_stream.scenario create mode 100644 webrtc/check/validate/signalling.py create mode 100644 webrtc/check/validate/testsuites/__init__.py create mode 100644 webrtc/check/validate/testsuites/webrtc.py create mode 100644 webrtc/check/validate/web/single_stream.html create mode 100644 webrtc/check/validate/web/webrtc.js create mode 100644 webrtc/check/validate/webrtc_validate.py diff --git a/webrtc/sendrecv/gst/tests/basic.py b/webrtc/check/basic.py similarity index 100% rename from webrtc/sendrecv/gst/tests/basic.py rename to webrtc/check/basic.py diff --git a/webrtc/sendrecv/gst/tests/meson.build b/webrtc/check/meson.build similarity index 100% rename from webrtc/sendrecv/gst/tests/meson.build rename to webrtc/check/meson.build diff --git a/webrtc/check/validate/README.md b/webrtc/check/validate/README.md new file mode 100644 index 0000000000..ea071532cc --- /dev/null +++ b/webrtc/check/validate/README.md @@ -0,0 +1,17 @@ +# What is this? + +The entire contents of this folder perform testing of GStreamer's webrtc +implementation against browser implementations using the selenium webdriver +testing framework. + +# Dependencies: + +- gst-validate: https://gitlab.freedesktop.org/gstreamer/gst-devtools/tree/master/validate +- gst-python: https://gitlab.freedesktop.org/gstreamer/gst-python/ +- selenium: https://www.seleniumhq.org/projects/webdriver/ +- selenium python bindings +- chrome and firefox with webdriver support + +# Run the tests + +`GST_VALIDATE_APPS_DIR=/path/to/gstwebrtc-demos/check/validate/apps/ GST_VALIDATE_SCENARIOS_PATH=/path/to/gstwebrtc-demos/check/validate/scenarios/ gst-validate-launcher --testsuites-dir /path/to/gstwebrtc-demos/check/validate/testsuites/ webrtc` diff --git a/webrtc/check/validate/actions.py b/webrtc/check/validate/actions.py new file mode 100644 index 0000000000..6ced2cfa7c --- /dev/null +++ b/webrtc/check/validate/actions.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import gi +gi.require_version("GstValidate", "1.0") +from gi.repository import GstValidate + +from observer import Signal + +class ActionObserver(object): + def __init__(self): + def _action_continue(val): + return val not in [GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED] + def _action_accum(previous, val): + # we want to always keep any errors propagated + if val in [GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED]: + return val + if previous in [GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED]: + return previous + + # we want to always prefer async returns + if previous in [GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED]: + return previous + if val in [GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED]: + return val + + return val + + self.create_offer = Signal(_action_continue, _action_accum) + self.wait_for_negotiation_state = Signal(_action_continue, _action_accum) + self.add_stream = Signal(_action_continue, _action_accum) + self.wait_for_remote_state = Signal(_action_continue, _action_accum) + + def _create_offer(self, scenario, action): + print("action create-offer") + return self.create_offer.fire() + def _wait_for_negotiation_state(self, scenario, action): + state = action.structure["state"] + print("action wait-for-negotiation-state", state) + return self.wait_for_negotiation_state.fire(state) + def _add_stream(self, scenario, action): + pipeline = action.structure["pipeline"] + print("action add-stream", pipeline) + return self.add_stream.fire(pipeline) + +def register_action_types(observer): + if not isinstance(observer, ActionObserver): + raise TypeError + + GstValidate.register_action_type("create-offer", "webrtc", + observer._create_offer, None, + "Instruct a create-offer to commence", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type("wait-for-negotiation-state", "webrtc", + observer._wait_for_negotiation_state, None, + "Wait for a specific negotiation state to be reached", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type("add-stream", "webrtc", + observer._add_stream, None, + "Add a stream to the webrtcbin", + GstValidate.ActionTypeFlags.NONE) diff --git a/webrtc/check/validate/apps/__init__.py b/webrtc/check/validate/apps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/webrtc/check/validate/apps/gstwebrtc.py b/webrtc/check/validate/apps/gstwebrtc.py new file mode 100644 index 0000000000..693ac4aea6 --- /dev/null +++ b/webrtc/check/validate/apps/gstwebrtc.py @@ -0,0 +1,187 @@ +#*- vi:si:et:sw=4:sts=4:ts=4:syntax=python +# +# Copyright (c) 2018 Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import inspect +import os +import sys +import shutil + +import tempfile + +from launcher.baseclasses import TestsManager, TestsGenerator, GstValidateTest, ScenarioManager +from launcher.utils import DEFAULT_TIMEOUT + +DEFAULT_BROWSERS = ['firefox', 'chrome'] +DEFAULT_SCENARIOS = [ + "offer_answer", + "vp8_send_stream" + ] + +BROWSER_SCENARIO_BLACKLISTS = { + 'firefox' : [ + 'offer_answer', # fails to accept an SDP without any media sections + ], + 'chrome' : [ + ], +} + +class MutableInt(object): + def __init__(self, value): + self.value = value + +class GstWebRTCTest(GstValidateTest): + __used_ports = set() + __last_id = MutableInt(10) + + @classmethod + def __get_open_port(cls): + while True: + # hackish trick from + # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python?answertab=votes#tab-top + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("", 0)) + port = s.getsockname()[1] + if port not in cls.__used_ports: + cls.__used_ports.add(port) + s.close() + return port + + s.close() + + @classmethod + def __get_available_peer_id(cls): + peerid = cls.__last_id.value + cls.__last_id.value += 2 + return peerid + + def __init__(self, classname, tests_manager, scenario, browser, timeout=DEFAULT_TIMEOUT): + super().__init__("python3", + classname, + tests_manager.options, + tests_manager.reporter, + timeout=timeout, + scenario=scenario) + self.webrtc_server = None + filename = inspect.getframeinfo (inspect.currentframe ()).filename + self.current_file_path = os.path.dirname (os.path.abspath (filename)) + self.certdir = None + self.browser = browser + + def launch_server(self): + if self.options.redirect_logs == 'stdout': + self.webrtcserver_logs = sys.stdout + elif self.options.redirect_logs == 'stderr': + self.webrtcserver_logs = sys.stderr + else: + self.webrtcserver_logs = open(self.logfile + '_webrtcserver.log', 'w+') + self.extra_logfiles.add(self.webrtcserver_logs.name) + + generate_certs_location = os.path.join(self.current_file_path, "..", "..", "..", "signalling", "generate_cert.sh") + self.certdir = tempfile.mkdtemp() + command = [generate_certs_location, self.certdir] + + server_env = os.environ.copy() + + subprocess.run(command, + stderr=self.webrtcserver_logs, + stdout=self.webrtcserver_logs, + env=server_env) + + self.server_port = self.__get_open_port() + + server_location = os.path.join(self.current_file_path, "..", "..", "..", "signalling", "simple_server.py") + command = [server_location, "--cert-path", self.certdir, "--addr", "127.0.0.1", "--port", str(self.server_port)] + + self.webrtc_server = subprocess.Popen(command, + stderr=self.webrtcserver_logs, + stdout=self.webrtcserver_logs, + env=server_env) + while True: + s = socket.socket() + try: + s.connect((("127.0.0.1", self.server_port))) + break + except ConnectionRefusedError: + time.sleep(0.1) + continue + finally: + s.close() + + return ' '.join(command) + + def build_arguments(self): + gst_id = self.__get_available_peer_id() + web_id = gst_id + 1 + + self.add_arguments(os.path.join(self.current_file_path, '..', 'webrtc_validate.py')) + self.add_arguments('--server') + self.add_arguments("wss://127.0.0.1:%s" % (self.server_port,)) + self.add_arguments('--browser') + self.add_arguments(self.browser) + self.add_arguments("--html-source") + html_page = os.path.join(self.current_file_path, '..', 'web', 'single_stream.html') + html_params = '?server=127.0.0.1&port=' + str(self.server_port) + '&id=' + str(web_id) + self.add_arguments("file://" + html_page + html_params) + self.add_arguments('--peer-id') + self.add_arguments(str(web_id)) + self.add_arguments(str(gst_id)) + + def close_logfile(self): + super().close_logfile() + if not self.options.redirect_logs: + self.webrtcserver_logs.close() + + def process_update(self): + res = super().process_update() + if res: + kill_subprocess(self, self.webrtc_server, DEFAULT_TIMEOUT) + self.__used_ports.remove(self.server_port) + if self.certdir: + shutil.rmtree(self.certdir, ignore_errors=True) + self.certdir + + return res + +class GstWebRTCTestsManager(TestsManager): + scenarios_manager = ScenarioManager() + name = "webrtc" + + def __init__(self): + super(GstWebRTCTestsManager, self).__init__() + self.loading_testsuite = self.name + + def webrtc_server_address(self): + return "wss://127.0.0.1:8443" + + def populate_testsuite(self): + self.add_scenarios (DEFAULT_SCENARIOS) + + scenarios = [(scenario_name, self.scenarios_manager.get_scenario(scenario_name)) + for scenario_name in self.get_scenarios()] + + for name, scenario in scenarios: + if not scenario: + self.warning("Could not find scenario %s" % name) + continue + for browser in DEFAULT_BROWSERS: + if name in BROWSER_SCENARIO_BLACKLISTS[browser]: + self.warning('Skipping broken test', name, 'for browser', browser) + continue + classname = browser + '_' + name + self.add_test(GstWebRTCTest(classname, self, scenario, browser)) diff --git a/webrtc/check/validate/browser.py b/webrtc/check/validate/browser.py new file mode 100644 index 0000000000..536a0319c4 --- /dev/null +++ b/webrtc/check/validate/browser.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from selenium import webdriver +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from selenium.webdriver.chrome.options import Options as COptions + +def create_firefox_driver(): + capabilities = webdriver.DesiredCapabilities().FIREFOX.copy() + capabilities['acceptSslCerts'] = True + profile = FirefoxProfile() + profile.set_preference ('media.navigator.streams.fake', True) + profile.set_preference ('media.navigator.permission.disabled', True) + + return webdriver.Firefox(firefox_profile=profile, capabilities=capabilities) + +def create_chrome_driver(): + capabilities = webdriver.DesiredCapabilities().CHROME.copy() + capabilities['acceptSslCerts'] = True + copts = COptions() + copts.add_argument('--allow-file-access-from-files') + copts.add_argument('--use-fake-ui-for-media-stream') + copts.add_argument('--use-fake-device-for-media-stream') + copts.add_argument('--enable-blink-features=RTCUnifiedPlanByDefault') + + return webdriver.Chrome(options=copts, desired_capabilities=capabilities) + +def create_driver(name): + if name == 'firefox': + return create_firefox_driver() + elif name == 'chrome': + return create_chrome_driver() + else: + raise ValueError("Unknown browser name " + name) + +def valid_int(n): + if isinstance(n, int): + return True + if isinstance(n, str): + try: + num = int(n) + return True + except: + return False + return False + +class Browser(object): + def __init__(self, driver, html_source): + self.driver = driver + self.html_source = html_source + + def get_peer_id(self): + self.driver.get(self.html_source) + peer_id = WebDriverWait(self.driver, 10).until( + lambda x: x.find_element_by_id('peer-id'), + message='Peer-id element was never seen' + ) + WebDriverWait (self.driver, 10).until( + lambda x: valid_int(peer_id.text), + message='Peer-id never became a number' + ) + return int(peer_id.text) diff --git a/webrtc/check/validate/client.py b/webrtc/check/validate/client.py new file mode 100644 index 0000000000..24c8bcd7bc --- /dev/null +++ b/webrtc/check/validate/client.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import threading +import copy + +from observer import Signal +from enums import NegotiationState + +import gi +gi.require_version("Gst", "1.0") +from gi.repository import Gst +gi.require_version("GstWebRTC", "1.0") +from gi.repository import GstWebRTC +gi.require_version("GstSdp", "1.0") +from gi.repository import GstSdp +gi.require_version("GstValidate", "1.0") +from gi.repository import GstValidate + +class WebRTCBinObserver(object): + def __init__(self, element): + self.state = NegotiationState.NEW + self.state_cond = threading.Condition() + self.element = element + self.signal_handlers = [] + self.signal_handlers.append(element.connect("on-negotiation-needed", self._on_negotiation_needed)) + self.signal_handlers.append(element.connect("on-ice-candidate", self._on_ice_candidate)) + self.signal_handlers.append(element.connect("pad-added", self._on_pad_added)) + self.signal_handlers.append(element.connect("on-new-transceiver", self._on_new_transceiver)) + self.signal_handlers.append(element.connect("on-data-channel", self._on_data_channel)) + self.on_offer_created = Signal() + self.on_answer_created = Signal() + self.on_negotiation_needed = Signal() + self.on_ice_candidate = Signal() + self.on_pad_added = Signal() + self.on_new_transceiver = Signal() + self.on_data_channel = Signal() + self.on_local_description_set = Signal() + self.on_remote_description_set = Signal() + + def _on_negotiation_needed(self, element): + self.on_negotiation_needed.fire() + + def _on_ice_candidate(self, element, mline, candidate): + self.on_ice_candidate.fire(mline, candidate) + + def _on_pad_added(self, element, pad): + self.on_pad_added.fire(pad) + + def _on_local_description_set(self, promise, desc): + self._update_negotiation_from_description_state(desc) + self.on_local_description_set.fire(desc) + + def _on_remote_description_set(self, promise, desc): + self._update_negotiation_from_description_state(desc) + self.on_remote_description_set.fire(desc) + + def _on_new_transceiver(self, element, transceiver): + self.on_new_transceiver.fire(transceiver) + + def _on_data_channel(self, element): + self.on_data_channel.fire(desc) + + def _update_negotiation_state(self, new_state): + with self.state_cond: + old_state = self.state + self.state = new_state + self.state_cond.notify_all() + print ("observer updated state to", new_state) + + def wait_for_negotiation_states(self, states): + ret = None + with self.state_cond: + while self.state not in states: + self.state_cond.wait() + print ("observer waited for", states) + ret = self.state + return ret + + def _update_negotiation_from_description_state(self, desc): + new_state = None + if desc.type == GstWebRTC.WebRTCSDPType.OFFER: + new_state = NegotiationState.OFFER_SET + elif desc.type == GstWebRTC.WebRTCSDPType.ANSWER: + new_state = NegotiationState.ANSWER_SET + assert new_state is not None + self._update_negotiation_state(new_state) + + def _deepcopy_session_description(self, desc): + # XXX: passing 'offer' to both a promise and an action signal without + # a deepcopy will segfault... + new_sdp = GstSdp.SDPMessage.new()[1] + GstSdp.sdp_message_parse_buffer(bytes(desc.sdp.as_text().encode()), new_sdp) + return GstWebRTC.WebRTCSessionDescription.new(desc.type, new_sdp) + + def _on_offer_created(self, promise, element): + self._update_negotiation_state(NegotiationState.OFFER_CREATED) + reply = promise.get_reply() + offer = reply['offer'] + + new_offer = self._deepcopy_session_description(offer) + promise = Gst.Promise.new_with_change_func(self._on_local_description_set, new_offer) + + new_offer = self._deepcopy_session_description(offer) + self.element.emit('set-local-description', new_offer, promise) + + self.on_offer_created.fire(offer) + + def _on_answer_created(self, promise, element): + self._update_negotiation_state(NegotiationState.ANSWER_CREATED) + reply = promise.get_reply() + offer = reply['answer'] + + new_offer = self._deepcopy_session_description(offer) + promise = Gst.Promise.new_with_change_func(self._on_local_description_set, new_offer) + + new_offer = self._deepcopy_session_description(offer) + self.element.emit('set-local-description', new_offer, promise) + + self.on_answer_created.fire(offer) + + def create_offer(self, options=None): + promise = Gst.Promise.new_with_change_func(self._on_offer_created, self.element) + self.element.emit('create-offer', options, promise) + + def create_answer(self, options=None): + promise = Gst.Promise.new_with_change_func(self._on_answer_created, self.element) + self.element.emit('create-answer', options, promise) + + def set_remote_description(self, desc): + promise = Gst.Promise.new_with_change_func(self._on_remote_description_set, desc) + self.element.emit('set-remote-description', desc, promise) + + def add_ice_candidate(self, mline, candidate): + self.element.emit('add-ice-candidate', mline, candidate) + +class WebRTCStream(object): + def __init__(self): + self.bin = None + + def set_description(self, desc): + assert self.bin is None + self.bin = Gst.parse_bin_from_description(desc, True) + + def add_and_link(self, parent, link): + assert self.bin is not None + self.bin.set_locked_state(True) + parent.add(self.bin) + src = self.bin.get_static_pad("src") + sink = self.bin.get_static_pad("sink") + assert src is None or sink is None + if src: + self.bin.link(link) + if sink: + link.link(self.bin) + self.bin.set_locked_state(False) + self.bin.sync_state_with_parent() + + def add_and_link_to(self, parent, link, pad): + assert self.bin is not None + self.bin.set_locked_state(True) + parent.add(self.bin) + src = self.bin.get_static_pad("src") + sink = self.bin.get_static_pad("sink") + assert src is None or sink is None + if pad.get_direction() == Gst.PadDirection.SRC: + assert sink is not None + pad.link(sink) + if pad.get_direction() == Gst.PadDirection.SINK: + assert src is not None + src.link(pad) + self.bin.set_locked_state(False) + self.bin.sync_state_with_parent() + +class WebRTCClient(WebRTCBinObserver): + def __init__(self): + self.pipeline = Gst.Pipeline(None) + self.webrtcbin = Gst.ElementFactory.make("webrtcbin") + super().__init__(self.webrtcbin) + self.pipeline.add(self.webrtcbin) + self._streams = [] + + def stop(self): + self.pipeline.set_state (Gst.State.NULL) + + def add_stream(self, desc): + stream = WebRTCStream() + stream.set_description(desc) + stream.add_and_link (self.pipeline, self.webrtcbin) + self._streams.append(stream) + + def add_stream_with_pad(self, desc, pad): + stream = WebRTCStream() + stream.set_description(desc) + stream.add_and_link_to (self.pipeline, self.webrtcbin, pad) + self._streams.append(stream) diff --git a/webrtc/check/validate/enums.py b/webrtc/check/validate/enums.py new file mode 100644 index 0000000000..14bc31a49d --- /dev/null +++ b/webrtc/check/validate/enums.py @@ -0,0 +1,36 @@ +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +class SignallingState(object): + NEW = "new" # no connection has been made + OPEN = "open" # websocket connection is open + ERROR = "error" # and error was thrown. overrides all others + HELLO = "hello" # hello was sent and received + SESSION = "session" # session setup was sent and received + +class NegotiationState(object): + NEW = "new" + ERROR = "error" + NEGOTIATION_NEEDED = "negotiation-needed" + OFFER_CREATED = "offer-created" + ANSWER_CREATED = "answer-created" + OFFER_SET = "offer-set" + ANSWER_SET = "answer-set" + +class RemoteState(object): + ERROR = "error" + REMOTE_STREAM_RECEIVED = "remote-stream-received" diff --git a/webrtc/check/validate/observer.py b/webrtc/check/validate/observer.py new file mode 100644 index 0000000000..b4f6be512e --- /dev/null +++ b/webrtc/check/validate/observer.py @@ -0,0 +1,43 @@ +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +class Signal(object): + def __init__(self, cont_func=None, accum_func=None): + self._handlers = [] + if not cont_func: + # by default continue when None/no return value is provided or + # True is returned + cont_func = lambda x: x is None or x + self.cont_func = cont_func + # default to accumulating truths + if not accum_func: + accum_func = lambda prev, v: prev and v + self.accum_func = accum_func + + def connect(self, handler): + self._handlers.append(handler) + + def disconnect(self, handler): + self._handlers.remove(handler) + + def fire(self, *args): + ret = None + for handler in self._handlers: + ret = self.accum_func(ret, handler(*args)) + if not self.cont_func(ret): + break + return ret diff --git a/webrtc/check/validate/scenarios/offer_answer.scenario b/webrtc/check/validate/scenarios/offer_answer.scenario new file mode 100644 index 0000000000..559f65fad7 --- /dev/null +++ b/webrtc/check/validate/scenarios/offer_answer.scenario @@ -0,0 +1,3 @@ +description, summary="Produce an offer" +create-offer; +wait-for-negotiation-state, state="answer-set" diff --git a/webrtc/check/validate/scenarios/vp8_send_stream.scenario b/webrtc/check/validate/scenarios/vp8_send_stream.scenario new file mode 100644 index 0000000000..2ec15a5391 --- /dev/null +++ b/webrtc/check/validate/scenarios/vp8_send_stream.scenario @@ -0,0 +1,5 @@ +description, summary="Send a VP8 stream", handles-state=true +add-stream, pipeline="videotestsrc is-live=1 ! vp8enc ! rtpvp8pay ! queue" +set-state, state="playing"; +create-offer; +wait-for-negotiation-state, state="answer-set" diff --git a/webrtc/check/validate/signalling.py b/webrtc/check/validate/signalling.py new file mode 100644 index 0000000000..2a7d9388b8 --- /dev/null +++ b/webrtc/check/validate/signalling.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import websockets +import asyncio +import ssl +import os +import sys +import threading +import json + +from observer import Signal +from enums import SignallingState, RemoteState + +class AsyncIOThread(threading.Thread): + def __init__ (self, loop): + threading.Thread.__init__(self) + self.loop = loop + + def run(self): + asyncio.set_event_loop(self.loop) + self.loop.run_forever() + + def stop_thread(self): + self.loop.call_soon_threadsafe(self.loop.stop) + +class SignallingClientThread(object): + def __init__(self, server): + self.server = server + + self.wss_connected = Signal() + self.message = Signal() + + self._init_async() + + def _init_async(self): + self.conn = None + self._loop = asyncio.new_event_loop() + + self._thread = AsyncIOThread(self._loop) + self._thread.start() + + self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(self._a_loop())) + + async def _a_connect(self): + assert not self.conn + sslctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) + self.conn = await websockets.connect(self.server, ssl=sslctx) + + async def _a_loop(self): + await self._a_connect() + self.wss_connected.fire() + assert self.conn + async for message in self.conn: + self.message.fire(message) + + def send(self, data): + async def _a_send(): + await self.conn.send(data) + self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_send())) + + def stop(self): + cond = threading.Condition() + + # asyncio, why you so complicated to stop ? + tasks = asyncio.all_tasks(self._loop) + async def _a_stop(): + if self.conn: + await self.conn.close() + self.conn = None + + to_wait = [t for t in tasks if not t.done()] + if to_wait: + done, pending = await asyncio.wait(to_wait) + with cond: + cond.notify() + + self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_stop())) + with cond: + cond.wait() + self._thread.stop_thread() + self._thread.join() + +class WebRTCSignallingClient(SignallingClientThread): + def __init__(self, server, id_): + super().__init__(server) + + self.wss_connected.connect(self._on_connection) + self.message.connect(self._on_message) + self.state = SignallingState.NEW + self._state_cond = threading.Condition() + + self.id = id_ + self._peerid = None + + # override that base class + self.connected = Signal() + self.session_created = Signal() + self.error = Signal() + self.have_json = Signal() + + def wait_for_states(self, states): + ret = None + with self._state_cond: + while self.state not in states: + self._state_cond.wait() + ret = self.state + return ret + + def _update_state(self, state): + with self._state_cond: + if self.state is not SignallingState.ERROR: + self.state = state + self._state_cond.notify_all() + + def hello(self): + self.send('HELLO ' + str(self.id)) + self.wait_for_states([SignallingState.HELLO]) + print("signalling-client sent HELLO") + + def create_session(self, peerid): + self._peerid = peerid + self.send('SESSION {}'.format(self._peerid)) + self.wait_for_states([SignallingState.SESSION, SignallingState.ERROR]) + print("signalling-client sent SESSION") + + def _on_connection(self): + self._update_state (SignallingState.OPEN) + + def _on_message(self, message): + print("signalling-client received", message) + if message == 'HELLO': + self._update_state (SignallingState.HELLO) + self.connected.fire() + elif message == 'SESSION_OK': + self._update_state (SignallingState.SESSION) + self.session_created.fire() + elif message.startswith('ERROR'): + self._update_state (SignallingState.ERROR) + self.error.fire(message) + else: + msg = json.loads(message) + self.have_json.fire(msg) + return False + diff --git a/webrtc/check/validate/testsuites/__init__.py b/webrtc/check/validate/testsuites/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/webrtc/check/validate/testsuites/webrtc.py b/webrtc/check/validate/testsuites/webrtc.py new file mode 100644 index 0000000000..c078e5d314 --- /dev/null +++ b/webrtc/check/validate/testsuites/webrtc.py @@ -0,0 +1,36 @@ +#*- vi:si:et:sw=4:sts=4:ts=4:syntax=python +# +# Copyright (c) 2018 Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" +The GstValidate webrtc streams testsuite +""" + +import os + +TEST_MANAGER = "webrtc" + +BLACKLIST = [ + ] + +def setup_tests(test_manager, options): + print("Setting up webrtc tests") +# test_manager.set_default_blacklist(BLACKLIST) + + return True + diff --git a/webrtc/check/validate/web/single_stream.html b/webrtc/check/validate/web/single_stream.html new file mode 100644 index 0000000000..9a95fb7491 --- /dev/null +++ b/webrtc/check/validate/web/single_stream.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
Status: unknown
+
+
Our id is unknown
+
+
+
getUserMedia constraints being used:
+
+
+ + diff --git a/webrtc/check/validate/web/webrtc.js b/webrtc/check/validate/web/webrtc.js new file mode 100644 index 0000000000..eb7e00f5e0 --- /dev/null +++ b/webrtc/check/validate/web/webrtc.js @@ -0,0 +1,320 @@ +/* vim: set sts=4 sw=4 et : + * + * Demo Javascript app for negotiating and streaming a sendrecv webrtc stream + * with a GStreamer app. Runs only in passive mode, i.e., responds to offers + * with answers, exchanges ICE candidates, and streams. + * + * Author: Nirbheek Chauhan + */ + +// Set this to override the automatic detection in websocketServerConnect() +var ws_server; +var ws_port; +// Set this to use a specific peer id instead of a random one +var default_peer_id; +// Override with your own STUN servers if you want +var rtc_configuration = {iceServers: [{urls: "stun:stun.services.mozilla.com"}, + {urls: "stun:stun.l.google.com:19302"}]}; +// The default constraints that will be attempted. Can be overriden by the user. +var default_constraints = {video: true, audio: true}; + +var connect_attempts = 0; +var peer_connection; +var send_channel; +var ws_conn; +// Promise for local stream after constraints are approved by the user +var local_stream_promise; + +function getOurId() { + return Math.floor(Math.random() * (9000 - 10) + 10).toString(); +} + +function resetState() { + // This will call onServerClose() + ws_conn.close(); +} + +function handleIncomingError(error) { + setError("ERROR: " + error); + resetState(); +} + +function getVideoElement() { + return document.getElementById("stream"); +} + +function setStatus(text) { + console.log(text); + var span = document.getElementById("status") + // Don't set the status if it already contains an error + if (!span.classList.contains('error')) + span.textContent = text; +} + +function setError(text) { + console.error(text); + var span = document.getElementById("status") + span.textContent = text; + span.classList.add('error'); + ws_conn.send(JSON.stringify({'STATE': 'error'})) +} + +function resetVideo() { + // Release the webcam and mic + if (local_stream_promise) + local_stream_promise.then(stream => { + if (stream) { + stream.getTracks().forEach(function (track) { track.stop(); }); + } + }); + + // Reset the video element and stop showing the last received frame + var videoElement = getVideoElement(); + videoElement.pause(); + videoElement.src = ""; + videoElement.load(); +} + +// SDP offer received from peer, set remote description and create an answer +function onIncomingSDP(sdp) { + peer_connection.setRemoteDescription(sdp).then(() => { + setStatus("Remote SDP set"); + if (sdp.type != "offer") + return; + setStatus("Got SDP offer"); + local_stream_promise.then((stream) => { + setStatus("Got local stream, creating answer"); + peer_connection.createAnswer() + .then(onLocalDescription).catch(setError); + }).catch(setError); + }).catch(setError); +} + +// Local description was set, send it to peer +function onLocalDescription(desc) { + console.log("Got local description: " + JSON.stringify(desc)); + peer_connection.setLocalDescription(desc).then(function() { + setStatus("Sending SDP answer"); + sdp = {'sdp': peer_connection.localDescription} + ws_conn.send(JSON.stringify(sdp)); + }); +} + +// ICE candidate received from peer, add it to the peer connection +function onIncomingICE(ice) { + var candidate = new RTCIceCandidate(ice); + peer_connection.addIceCandidate(candidate).catch(setError); +} + +function onServerMessage(event) { + console.log("Received " + event.data); + switch (event.data) { + case "HELLO": + setStatus("Registered with server, waiting for call"); + return; + default: + if (event.data.startsWith("ERROR")) { + 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); + } + return; + } + + // 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); + } + } +} + +function onServerClose(event) { + setStatus('Disconnected from server'); + resetVideo(); + + if (peer_connection) { + peer_connection.close(); + peer_connection = null; + } + + // Reset after a second + window.setTimeout(websocketServerConnect, 1000); +} + +function onServerError(event) { + setError("Unable to connect to server, did you add an exception for the certificate?") + // Retry after 3 seconds + window.setTimeout(websocketServerConnect, 3000); +} + +function getLocalStream() { + var constraints; + var textarea = document.getElementById('constraints'); + try { + constraints = JSON.parse(textarea.value); + } catch (e) { + console.error(e); + setError('ERROR parsing constraints: ' + e.message + ', using default constraints'); + constraints = default_constraints; + } + console.log(JSON.stringify(constraints)); + + // Add local stream + if (navigator.mediaDevices.getUserMedia) { + return navigator.mediaDevices.getUserMedia(constraints); + } else { + errorUserMediaHandler(); + } +} + +function websocketServerConnect() { + connect_attempts++; + if (connect_attempts > 3) { + setError("Too many connection attempts, aborting. Refresh page to try again"); + return; + } + // Clear errors in the status span + var span = document.getElementById("status"); + span.classList.remove('error'); + span.textContent = ''; + // Populate constraints + var textarea = document.getElementById('constraints'); + if (textarea.value == '') + textarea.value = JSON.stringify(default_constraints); + // Fetch the peer id to use + + var url = new URL(window.location.href); + + peer_id = url.searchParams.get("id"); + peer_id = peer_id || default_peer_id || getOurId(); + + ws_port = ws_port || url.searchParams.get("port"); + ws_port = ws_port || '8443'; + + ws_server = ws_server || url.searchParams.get("server"); + if (window.location.protocol.startsWith ("file")) { + ws_server = ws_server || "127.0.0.1"; + } else if (window.location.protocol.startsWith ("http")) { + ws_server = ws_server || window.location.hostname; + } else { + throw new Error ("Don't know how to connect to the signalling server with uri" + window.location); + } + + var ws_url = 'wss://' + ws_server + ':' + ws_port + setStatus("Connecting to server " + ws_url); + ws_conn = new WebSocket(ws_url); + /* When connected, immediately register with the server */ + ws_conn.addEventListener('open', (event) => { + document.getElementById("peer-id").textContent = peer_id; + ws_conn.send('HELLO ' + peer_id); + setStatus("Registering with server"); + }); + ws_conn.addEventListener('error', onServerError); + ws_conn.addEventListener('message', onServerMessage); + ws_conn.addEventListener('close', onServerClose); +} + +function onRemoteStreamAdded(event) { + videoTracks = event.stream.getVideoTracks(); + audioTracks = event.stream.getAudioTracks(); + + if (videoTracks.length > 0) { + console.log('Incoming stream: ' + videoTracks.length + ' video tracks and ' + audioTracks.length + ' audio tracks'); + getVideoElement().srcObject = event.stream; + ws_conn.send(JSON.stringify({'STATE': 'remote-stream-received'})) + } else { + handleIncomingError('Stream with unknown tracks added, resetting'); + } +} + +function errorUserMediaHandler() { + setError("Browser doesn't support getUserMedia!"); +} + +const handleDataChannelOpen = (event) =>{ + console.log("dataChannel.OnOpen", event); +}; + +const handleDataChannelMessageReceived = (event) =>{ + console.log("dataChannel.OnMessage:", event, event.data.type); + + setStatus("Received data channel message"); + if (typeof event.data === 'string' || event.data instanceof String) { + console.log('Incoming string message: ' + event.data); + textarea = document.getElementById("text") + textarea.value = textarea.value + '\n' + event.data + } else { + console.log('Incoming data message'); + } + send_channel.send("Hi! (from browser)"); +}; + +const handleDataChannelError = (error) =>{ + console.log("dataChannel.OnError:", error); +}; + +const handleDataChannelClose = (event) =>{ + console.log("dataChannel.OnClose", event); +}; + +function onDataChannel(event) { + setStatus("Data channel created"); + let receiveChannel = event.channel; + receiveChannel.onopen = handleDataChannelOpen; + receiveChannel.onmessage = handleDataChannelMessageReceived; + receiveChannel.onerror = handleDataChannelError; + receiveChannel.onclose = handleDataChannelClose; +} + +function createCall(msg) { + // Reset connection attempts because we connected successfully + connect_attempts = 0; + + console.log('Creating RTCPeerConnection'); + + peer_connection = new RTCPeerConnection(rtc_configuration); + send_channel = peer_connection.createDataChannel('label', null); + send_channel.onopen = handleDataChannelOpen; + send_channel.onmessage = handleDataChannelMessageReceived; + send_channel.onerror = handleDataChannelError; + send_channel.onclose = handleDataChannelClose; + peer_connection.ondatachannel = onDataChannel; + peer_connection.onaddstream = onRemoteStreamAdded; + /* Send our video/audio to the other peer */ + local_stream_promise = getLocalStream().then((stream) => { + console.log('Adding local stream'); + peer_connection.addStream(stream); + return stream; + }).catch(setError); + + if (!msg.sdp) { + console.log("WARNING: First message wasn't an SDP message!?"); + } + + peer_connection.onicecandidate = (event) => { + // We have a candidate, send it to the remote party with the + // same uuid + if (event.candidate == null) { + console.log("ICE Candidate was null, done"); + return; + } + ws_conn.send(JSON.stringify({'ice': event.candidate})); + }; + + setStatus("Created peer connection for call, waiting for SDP"); +} diff --git a/webrtc/check/validate/webrtc_validate.py b/webrtc/check/validate/webrtc_validate.py new file mode 100644 index 0000000000..6215a0a2e2 --- /dev/null +++ b/webrtc/check/validate/webrtc_validate.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018, Matthew Waters +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import sys +import argparse +import json + +from signalling import WebRTCSignallingClient +from actions import register_action_types, ActionObserver +from client import WebRTCClient +from browser import Browser, create_driver +from enums import SignallingState, NegotiationState, RemoteState + +import gi +gi.require_version("GLib", "2.0") +from gi.repository import GLib +gi.require_version("Gst", "1.0") +from gi.repository import Gst +gi.require_version("GstWebRTC", "1.0") +from gi.repository import GstWebRTC +gi.require_version("GstSdp", "1.0") +from gi.repository import GstSdp +gi.require_version("GstValidate", "1.0") +from gi.repository import GstValidate + +class WebRTCApplication(object): + def __init__(self, server, id_, peerid, scenario_name, browser_name, html_source): + self.server = server + self.peerid = peerid + self.html_source = html_source + self.id = id_ + self.scenario_name = scenario_name + self.browser_name = browser_name + + def _init_validate(self, scenario_file): + self.runner = GstValidate.Runner.new() + self.monitor = GstValidate.Monitor.factory_create( + self.client.pipeline, self.runner, None) + self._scenario = GstValidate.Scenario.factory_create( + self.runner, self.client.pipeline, self.scenario_name) + self._scenario.connect("done", self._on_scenario_done) + self._scenario.props.execute_on_idle = True + if not self._scenario.props.handles_states: + self.client.pipeline.set_state(Gst.State.PLAYING) + + def _on_scenario_done(self, scenario): + self.quit() + + def _connect_actions(self): + def create_offer(): + self.client.create_offer(None) + return GstValidate.ActionReturn.OK + self.actions.create_offer.connect(create_offer) + + def wait_for_negotiation_state(state): + states = [state, NegotiationState.ERROR] + state = self.client.wait_for_negotiation_states(states) + return GstValidate.ActionReturn.OK if state != RemoteState.ERROR else GstValidate.ActionReturn.ERROR + self.actions.wait_for_negotiation_state.connect(wait_for_negotiation_state) + + def add_stream(pipeline): + self.client.add_stream(pipeline) + return GstValidate.ActionReturn.OK + self.actions.add_stream.connect(add_stream) + + def _connect_client_observer(self): + def on_offer_created(offer): + text = offer.sdp.as_text() + msg = json.dumps({'sdp': {'type': 'offer', 'sdp': text}}) + self.signalling.send(msg) + self.client.on_offer_created.connect(on_offer_created) + + def on_ice_candidate(mline, candidate): + msg = json.dumps({'ice': {'sdpMLineIndex': str(mline), 'candidate' : candidate}}) + self.signalling.send(msg) + self.client.on_ice_candidate.connect(on_ice_candidate) + + def on_pad_added(pad): + if pad.get_direction() != Gst.PadDirection.SRC: + return + self.client.add_stream_with_pad('fakesink', pad) + self.client.on_pad_added.connect(on_pad_added) + + def _connect_signalling_observer(self): + def have_json(msg): + if 'sdp' in msg: + sdp = msg['sdp'] + res, sdpmsg = GstSdp.SDPMessage.new() + GstSdp.sdp_message_parse_buffer(bytes(sdp['sdp'].encode()), sdpmsg) + sdptype = GstWebRTC.WebRTCSDPType.ANSWER if sdp['type'] == 'answer' else GstWebRTC.WebRTCSDPType.OFFER + desc = GstWebRTC.WebRTCSessionDescription.new(sdptype, sdpmsg) + self.client.set_remote_description(desc) + elif 'ice' in msg: + ice = msg['ice'] + candidate = ice['candidate'] + sdpmlineindex = ice['sdpMLineIndex'] + self.client.add_ice_candidate(sdpmlineindex, candidate) + self.signalling.have_json.connect(have_json) + + def error(msg): + # errors are unexpected + GLib.idle_add(self.quit) + GLib.idle_add(sys.exit, -20) + self.signalling.error.connect(error) + + def _init(self): + self.main_loop = GLib.MainLoop() + + self.client = WebRTCClient() + self._connect_client_observer() + + self.actions = ActionObserver() + register_action_types(self.actions) + self._connect_actions() + + self.signalling = WebRTCSignallingClient(self.server, self.id) + self._connect_signalling_observer() + self.signalling.wait_for_states([SignallingState.OPEN]) + self.signalling.hello() + + self.browser = Browser(create_driver(self.browser_name), self.html_source) + + browser_id = self.browser.get_peer_id () + assert browser_id == self.peerid + + self.signalling.create_session(self.peerid) + + self._init_validate(self.scenario_name) + print("app initialized") + + def quit(self): + # Stop signalling first so asyncio doesn't keep us alive on weird failures + self.signalling.stop() + self.browser.driver.quit() + self.client.stop() + self.main_loop.quit() + + def run(self): + try: + self._init() + self.main_loop.run() + except: + self.quit() + raise + +def parse_options(): + parser = argparse.ArgumentParser() + parser.add_argument('id', help='ID of this client', type=int) + parser.add_argument('--peer-id', help='ID of the peer to connect to', type=int) + parser.add_argument('--server', help='Signalling server to connect to, eg "wss://127.0.0.1:8443"') + parser.add_argument('--html-source', help='HTML page to open in the browser', default=None) + parser.add_argument('--scenario', help='Scenario file to execute', default=None) + parser.add_argument('--browser', help='Browser name to use', default=None) + return parser.parse_args() + +def init(): + Gst.init(None) + GstValidate.init() + + args = parse_options() + if not args.scenario: + args.scenario = os.environ.get('GST_VALIDATE_SCENARIO', None) + # if we have both manual scenario creation and env, then the scenario + # is executed twice... + if 'GST_VALIDATE_SCENARIO' in os.environ: + del os.environ['GST_VALIDATE_SCENARIO'] + if not args.scenario: + raise ValueError("No scenario file provided") + if not args.server: + raise ValueError("No server location provided") + if not args.peer_id: + raise ValueError("No peer id provided") + if not args.html_source: + raise ValueError("No HTML page provided") + if not args.browser: + raise ValueError("No Browser provided") + + return args + +def run(): + args = init() + w = WebRTCApplication (args.server, args.id, args.peer_id, args.scenario, args.browser, args.html_source) + return w.run() + +if __name__ == "__main__": + sys.exit(run()) diff --git a/webrtc/meson.build b/webrtc/meson.build index 3ee35f7bd9..db32d48174 100644 --- a/webrtc/meson.build +++ b/webrtc/meson.build @@ -37,3 +37,5 @@ endif subdir('multiparty-sendrecv') subdir('signalling') subdir('sendrecv') + +subdir('check') diff --git a/webrtc/sendrecv/gst/meson.build b/webrtc/sendrecv/gst/meson.build index 85950add4a..5c9509c7e1 100644 --- a/webrtc/sendrecv/gst/meson.build +++ b/webrtc/sendrecv/gst/meson.build @@ -3,5 +3,3 @@ executable('webrtc-sendrecv', dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, libsoup_dep, json_glib_dep ]) webrtc_py = files('webrtc_sendrecv.py') - -subdir('tests') From 615813ef93f6a7b977280bd55d6ffd17c25fcbd2 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 12 Feb 2020 21:56:34 +1100 Subject: [PATCH 342/412] check/validate: a few more tests and improvements Tests a matrix of options: - local/remote negotiation initiator - 'most' bundle-policy combinations (some combinations will never work) - firefox or chrome browser Across 4 test scenarios: - simple negotiation with default browser streams (or none if gstreamer initiates) - sending a vp8 stream - opening a data channel - sending a message over the data channel for a total of 112 tests! --- webrtc/check/validate/actions.py | 95 ++++++--- webrtc/check/validate/apps/gstwebrtc.py | 156 +++++++++++--- webrtc/check/validate/browser.py | 29 ++- webrtc/check/validate/client.py | 123 +++++++---- webrtc/check/validate/enums.py | 63 ++++-- webrtc/check/validate/observer.py | 128 +++++++++++- .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../bundle_policy.scenario | 1 + .../negotiation_initiator.scenario | 1 + .../validate/scenarios/offer_answer.scenario | 18 +- .../scenarios/open_data_channel.scenario | 23 ++ .../negotiation_initiator.scenario | 1 + .../send_data_channel_string.scenario | 21 ++ .../scenarios/vp8_send_stream.scenario | 14 +- webrtc/check/validate/signalling.py | 155 +++++++++++--- webrtc/check/validate/testsuites/webrtc.py | 7 +- webrtc/check/validate/web/single_stream.html | 4 - webrtc/check/validate/web/webrtc.js | 197 ++++++++++++------ webrtc/check/validate/webrtc_validate.py | 141 ++++++++++--- 29 files changed, 926 insertions(+), 262 deletions(-) create mode 100644 webrtc/check/validate/scenarios/bundle_local_balanced_remote_balanced/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_bundle/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_compat/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_balanced/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_bundle/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_compat/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_compat_remote_balanced/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_bundle/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_compat/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_none_remote_balanced/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_none_remote_max_bundle/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/bundle_local_none_remote_max_compat/bundle_policy.scenario create mode 100644 webrtc/check/validate/scenarios/local_initiates_negotiation/negotiation_initiator.scenario create mode 100644 webrtc/check/validate/scenarios/open_data_channel.scenario create mode 100644 webrtc/check/validate/scenarios/remote_initiates_negotiation/negotiation_initiator.scenario create mode 100644 webrtc/check/validate/scenarios/send_data_channel_string.scenario diff --git a/webrtc/check/validate/actions.py b/webrtc/check/validate/actions.py index 6ced2cfa7c..98a3cb837f 100644 --- a/webrtc/check/validate/actions.py +++ b/webrtc/check/validate/actions.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -22,6 +20,11 @@ gi.require_version("GstValidate", "1.0") from gi.repository import GstValidate from observer import Signal +from enums import Actions + +import logging + +l = logging.getLogger(__name__) class ActionObserver(object): def __init__(self): @@ -42,36 +45,62 @@ class ActionObserver(object): return val - self.create_offer = Signal(_action_continue, _action_accum) - self.wait_for_negotiation_state = Signal(_action_continue, _action_accum) - self.add_stream = Signal(_action_continue, _action_accum) - self.wait_for_remote_state = Signal(_action_continue, _action_accum) + self.action = Signal(_action_continue, _action_accum) - def _create_offer(self, scenario, action): - print("action create-offer") - return self.create_offer.fire() - def _wait_for_negotiation_state(self, scenario, action): - state = action.structure["state"] - print("action wait-for-negotiation-state", state) - return self.wait_for_negotiation_state.fire(state) - def _add_stream(self, scenario, action): - pipeline = action.structure["pipeline"] - print("action add-stream", pipeline) - return self.add_stream.fire(pipeline) + def _action(self, scenario, action): + l.debug('executing action: ' + str(action.structure)) + return self.action.fire (Actions(action.structure.get_name()), action) -def register_action_types(observer): - if not isinstance(observer, ActionObserver): - raise TypeError - GstValidate.register_action_type("create-offer", "webrtc", - observer._create_offer, None, - "Instruct a create-offer to commence", - GstValidate.ActionTypeFlags.NONE) - GstValidate.register_action_type("wait-for-negotiation-state", "webrtc", - observer._wait_for_negotiation_state, None, - "Wait for a specific negotiation state to be reached", - GstValidate.ActionTypeFlags.NONE) - GstValidate.register_action_type("add-stream", "webrtc", - observer._add_stream, None, - "Add a stream to the webrtcbin", - GstValidate.ActionTypeFlags.NONE) + def register_action_types(observer): + if not isinstance(observer, ActionObserver): + raise TypeError + + GstValidate.register_action_type(Actions.CREATE_OFFER.value, + "webrtc", observer._action, None, + "Instruct a create-offer to commence", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.CREATE_ANSWER.value, + "webrtc", observer._action, None, + "Create answer", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.WAIT_FOR_NEGOTIATION_STATE.value, + "webrtc", observer._action, None, + "Wait for a specific negotiation state to be reached", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.ADD_STREAM.value, + "webrtc", observer._action, None, + "Add a stream to the webrtcbin", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.ADD_DATA_CHANNEL.value, + "webrtc", observer._action, None, + "Add a data channel to the webrtcbin", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.SEND_DATA_CHANNEL_STRING.value, + "webrtc", observer._action, None, + "Send a message using a data channel", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.WAIT_FOR_DATA_CHANNEL_STATE.value, + "webrtc", observer._action, None, + "Wait for data channel to reach state", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.CLOSE_DATA_CHANNEL.value, + "webrtc", observer._action, None, + "Close a data channel", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.WAIT_FOR_DATA_CHANNEL.value, + "webrtc", observer._action, None, + "Wait for a data channel to appear", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.WAIT_FOR_DATA_CHANNEL_STRING.value, + "webrtc", observer._action, None, + "Wait for a data channel to receive a message", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.WAIT_FOR_NEGOTIATION_NEEDED.value, + "webrtc", observer._action, None, + "Wait for a the on-negotiation-needed signal to fire", + GstValidate.ActionTypeFlags.NONE) + GstValidate.register_action_type(Actions.SET_WEBRTC_OPTIONS.value, + "webrtc", observer._action, None, + "Set some webrtc options", + GstValidate.ActionTypeFlags.NONE) diff --git a/webrtc/check/validate/apps/gstwebrtc.py b/webrtc/check/validate/apps/gstwebrtc.py index 693ac4aea6..27d4088bb9 100644 --- a/webrtc/check/validate/apps/gstwebrtc.py +++ b/webrtc/check/validate/apps/gstwebrtc.py @@ -1,6 +1,4 @@ -#*- vi:si:et:sw=4:sts=4:ts=4:syntax=python -# -# Copyright (c) 2018 Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,26 +19,82 @@ import inspect import os import sys import shutil +import itertools import tempfile -from launcher.baseclasses import TestsManager, TestsGenerator, GstValidateTest, ScenarioManager +from launcher.baseclasses import TestsManager, GstValidateTest, ScenarioManager from launcher.utils import DEFAULT_TIMEOUT DEFAULT_BROWSERS = ['firefox', 'chrome'] -DEFAULT_SCENARIOS = [ - "offer_answer", - "vp8_send_stream" - ] -BROWSER_SCENARIO_BLACKLISTS = { - 'firefox' : [ - 'offer_answer', # fails to accept an SDP without any media sections - ], - 'chrome' : [ - ], +# list of scenarios. These are the names of the actual scenario files stored +# on disk. +DEFAULT_SCENARIOS = [ + "offer_answer", + "vp8_send_stream", + "open_data_channel", + "send_data_channel_string", + ] + +# various configuration changes that are included from other scenarios. +# key is the name of the override used in the name of the test +# value is the subdirectory where the override is placed +# changes some things about the test like: +# - who initiates the negotiation +# - bundle settings +SCENARIO_OVERRIDES = { + # name : directory + + # who starts the negotiation + 'local' : 'local_initiates_negotiation', + 'remote' : 'remote_initiates_negotiation', + + # bundle-policy configuration + # XXX: webrtcbin's bundle-policy=none is not part of the spec + 'none_compat' : 'bundle_local_none_remote_max_compat', + 'none_balanced' : 'bundle_local_none_remote_balanced', + 'none_bundle' : 'bundle_local_none_remote_max_bundle', + 'compat_compat' : 'bundle_local_max_compat_remote_max_compat', + 'compat_balanced' : 'bundle_local_max_compat_remote_balanced', + 'compat_bundle' : 'bundle_local_max_compat_remote_max_bundle', + 'balanced_compat' : 'bundle_local_balanced_remote_max_compat', + 'balanced_balanced' : 'bundle_local_balanced_remote_balanced', + 'balanced_bundle' : 'bundle_local_balanced_remote_bundle', + 'bundle_compat' : 'bundle_local_max_bundle_remote_max_compat', + 'bundle_balanced' : 'bundle_local_max_bundle_remote_balanced', + 'bundle_bundle' : 'bundle_local_max_bundle_remote_max_bundle', } +bundle_options = ['compat', 'balanced', 'bundle'] + +# Given an override, these are the choices to choose from. Each choice is a +# separate test +OVERRIDE_CHOICES = { + 'initiator' : ['local', 'remote'], + 'bundle' : ['_'.join(opt) for opt in itertools.product(['none'] + bundle_options, bundle_options)], +} + +# Which scenarios support which override. All the overrides will be chosen +SCENARIO_OVERRIDES_SUPPORTED = { + "offer_answer" : ['initiator', 'bundle'], + "vp8_send_stream" : ['initiator', 'bundle'], + "open_data_channel" : ['initiator', 'bundle'], + "send_data_channel_string" : ['initiator', 'bundle'], +} + +# Things that don't work for some reason or another. +DEFAULT_BLACKLIST = [ + (r"webrtc\.firefox\.local\..*offer_answer", + "Firefox doesn't like a SDP without any media"), + (r"webrtc.*remote.*vp8_send_stream", + "We can't match payload types with a remote offer and a sending stream"), + (r"webrtc.*\.balanced_.*", + "webrtcbin doesn't implement bundle-policy=balanced"), + (r"webrtc.*\.none_bundle.*", + "Browsers want a BUNDLE group if in max-bundle mode"), +] + class MutableInt(object): def __init__(self, value): self.value = value @@ -66,11 +120,12 @@ class GstWebRTCTest(GstValidateTest): @classmethod def __get_available_peer_id(cls): + # each connection uses two peer ids peerid = cls.__last_id.value cls.__last_id.value += 2 return peerid - def __init__(self, classname, tests_manager, scenario, browser, timeout=DEFAULT_TIMEOUT): + def __init__(self, classname, tests_manager, scenario, browser, scenario_override_includes=None, timeout=DEFAULT_TIMEOUT): super().__init__("python3", classname, tests_manager.options, @@ -82,6 +137,7 @@ class GstWebRTCTest(GstValidateTest): self.current_file_path = os.path.dirname (os.path.abspath (filename)) self.certdir = None self.browser = browser + self.scenario_override_includes = scenario_override_includes def launch_server(self): if self.options.redirect_logs == 'stdout': @@ -138,6 +194,8 @@ class GstWebRTCTest(GstValidateTest): html_page = os.path.join(self.current_file_path, '..', 'web', 'single_stream.html') html_params = '?server=127.0.0.1&port=' + str(self.server_port) + '&id=' + str(web_id) self.add_arguments("file://" + html_page + html_params) + self.add_arguments("--name") + self.add_arguments(self.classname) self.add_arguments('--peer-id') self.add_arguments(str(web_id)) self.add_arguments(str(gst_id)) @@ -154,10 +212,27 @@ class GstWebRTCTest(GstValidateTest): self.__used_ports.remove(self.server_port) if self.certdir: shutil.rmtree(self.certdir, ignore_errors=True) - self.certdir return res + def get_subproc_env(self): + env = super().get_subproc_env() + if not self.scenario_override_includes: + return env + + # this feels gross... + paths = env.get('GST_VALIDATE_SCENARIOS_PATH', '').split(os.pathsep) + new_paths = [] + for p in paths: + new_paths.append(p) + for override_path in self.scenario_override_includes: + new_p = os.path.join(p, override_path) + if os.path.exists (new_p): + new_paths.append(new_p) + env['GST_VALIDATE_SCENARIOS_PATH'] = os.pathsep.join(new_paths) + + return env + class GstWebRTCTestsManager(TestsManager): scenarios_manager = ScenarioManager() name = "webrtc" @@ -165,23 +240,50 @@ class GstWebRTCTestsManager(TestsManager): def __init__(self): super(GstWebRTCTestsManager, self).__init__() self.loading_testsuite = self.name + self._scenarios = [] - def webrtc_server_address(self): - return "wss://127.0.0.1:8443" + def add_scenarios(self, scenarios): + if isinstance(scenarios, list): + self._scenarios.extend(scenarios) + else: + self._scenarios.append(scenarios) + + self._scenarios = list(set(self._scenarios)) + + def set_scenarios(self, scenarios): + self._scenarios = [] + self.add_scenarios(scenarios) + + def get_scenarios(self): + return self._scenarios def populate_testsuite(self): self.add_scenarios (DEFAULT_SCENARIOS) + self.set_default_blacklist(DEFAULT_BLACKLIST) + + def list_tests(self): + if self.tests: + return self.tests scenarios = [(scenario_name, self.scenarios_manager.get_scenario(scenario_name)) for scenario_name in self.get_scenarios()] - for name, scenario in scenarios: - if not scenario: - self.warning("Could not find scenario %s" % name) - continue - for browser in DEFAULT_BROWSERS: - if name in BROWSER_SCENARIO_BLACKLISTS[browser]: - self.warning('Skipping broken test', name, 'for browser', browser) + for browser in DEFAULT_BROWSERS: + for name, scenario in scenarios: + if not scenario: + self.warning("Could not find scenario %s" % name) continue - classname = browser + '_' + name - self.add_test(GstWebRTCTest(classname, self, scenario, browser)) + if not SCENARIO_OVERRIDES_SUPPORTED[name]: + # no override choices supported + classname = browser + '.' + name + print ("adding", classname) + self.add_test(GstWebRTCTest(classname, self, scenario, browser)) + else: + for overrides in itertools.product(*[OVERRIDE_CHOICES[c] for c in SCENARIO_OVERRIDES_SUPPORTED[name]]): + oname = '.'.join (overrides) + opaths = [SCENARIO_OVERRIDES[p] for p in overrides] + classname = browser + '.' + oname + '.' + name + print ("adding", classname) + self.add_test(GstWebRTCTest(classname, self, scenario, browser, opaths)) + + return self.tests diff --git a/webrtc/check/validate/browser.py b/webrtc/check/validate/browser.py index 536a0319c4..7db9b0b60b 100644 --- a/webrtc/check/validate/browser.py +++ b/webrtc/check/validate/browser.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,11 +15,14 @@ # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301, USA. +import logging from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.firefox.firefox_profile import FirefoxProfile from selenium.webdriver.chrome.options import Options as COptions +l = logging.getLogger(__name__) + def create_firefox_driver(): capabilities = webdriver.DesiredCapabilities().FIREFOX.copy() capabilities['acceptSslCerts'] = True @@ -39,6 +40,9 @@ def create_chrome_driver(): copts.add_argument('--use-fake-ui-for-media-stream') copts.add_argument('--use-fake-device-for-media-stream') copts.add_argument('--enable-blink-features=RTCUnifiedPlanByDefault') + # XXX: until libnice can deal with mdns candidates + local_state = {"enabled_labs_experiments" : ["enable-webrtc-hide-local-ips-with-mdns@2"] } + copts.add_experimental_option("localState", {"browser" : local_state}) return webdriver.Chrome(options=copts, desired_capabilities=capabilities) @@ -48,7 +52,7 @@ def create_driver(name): elif name == 'chrome': return create_chrome_driver() else: - raise ValueError("Unknown browser name " + name) + raise ValueError("Unknown browser name \'" + name + "\'") def valid_int(n): if isinstance(n, int): @@ -62,18 +66,23 @@ def valid_int(n): return False class Browser(object): - def __init__(self, driver, html_source): + """ + A browser as connected through selenium. + """ + def __init__(self, driver): + l.info('Using driver \'' + driver.name + '\' with capabilities ' + str(driver.capabilities)) self.driver = driver - self.html_source = html_source + + def open(self, html_source): + self.driver.get(html_source) def get_peer_id(self): - self.driver.get(self.html_source) - peer_id = WebDriverWait(self.driver, 10).until( + peer_id = WebDriverWait(self.driver, 5).until( lambda x: x.find_element_by_id('peer-id'), message='Peer-id element was never seen' ) - WebDriverWait (self.driver, 10).until( + WebDriverWait (self.driver, 5).until( lambda x: valid_int(peer_id.text), message='Peer-id never became a number' ) - return int(peer_id.text) + return int(peer_id.text) diff --git a/webrtc/check/validate/client.py b/webrtc/check/validate/client.py index 24c8bcd7bc..4a19982f14 100644 --- a/webrtc/check/validate/client.py +++ b/webrtc/check/validate/client.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -20,8 +18,8 @@ import threading import copy -from observer import Signal -from enums import NegotiationState +from observer import Signal, WebRTCObserver, DataChannelObserver, StateObserver +from enums import NegotiationState, DataChannelState import gi gi.require_version("Gst", "1.0") @@ -33,10 +31,13 @@ from gi.repository import GstSdp gi.require_version("GstValidate", "1.0") from gi.repository import GstValidate -class WebRTCBinObserver(object): + +class WebRTCBinObserver(WebRTCObserver): + """ + Observe a webrtcbin element. + """ def __init__(self, element): - self.state = NegotiationState.NEW - self.state_cond = threading.Condition() + WebRTCObserver.__init__(self) self.element = element self.signal_handlers = [] self.signal_handlers.append(element.connect("on-negotiation-needed", self._on_negotiation_needed)) @@ -44,17 +45,16 @@ class WebRTCBinObserver(object): self.signal_handlers.append(element.connect("pad-added", self._on_pad_added)) self.signal_handlers.append(element.connect("on-new-transceiver", self._on_new_transceiver)) self.signal_handlers.append(element.connect("on-data-channel", self._on_data_channel)) - self.on_offer_created = Signal() - self.on_answer_created = Signal() + self.negotiation_needed = 0 + self._negotiation_needed_observer = StateObserver(self, "negotiation_needed", threading.Condition()) self.on_negotiation_needed = Signal() self.on_ice_candidate = Signal() self.on_pad_added = Signal() self.on_new_transceiver = Signal() - self.on_data_channel = Signal() - self.on_local_description_set = Signal() - self.on_remote_description_set = Signal() def _on_negotiation_needed(self, element): + self.negotiation_needed += 1 + self._negotiation_needed_observer.update(self.negotiation_needed) self.on_negotiation_needed.fire() def _on_ice_candidate(self, element, mline, candidate): @@ -63,35 +63,19 @@ class WebRTCBinObserver(object): def _on_pad_added(self, element, pad): self.on_pad_added.fire(pad) - def _on_local_description_set(self, promise, desc): - self._update_negotiation_from_description_state(desc) - self.on_local_description_set.fire(desc) - - def _on_remote_description_set(self, promise, desc): - self._update_negotiation_from_description_state(desc) - self.on_remote_description_set.fire(desc) + def _on_description_set(self, promise, desc): + new_state = self._update_negotiation_from_description_state(desc) + if new_state == NegotiationState.OFFER_SET: + self.on_offer_set.fire (desc) + elif new_state == NegotiationState.ANSWER_SET: + self.on_answer_set.fire (desc) def _on_new_transceiver(self, element, transceiver): self.on_new_transceiver.fire(transceiver) - def _on_data_channel(self, element): - self.on_data_channel.fire(desc) - - def _update_negotiation_state(self, new_state): - with self.state_cond: - old_state = self.state - self.state = new_state - self.state_cond.notify_all() - print ("observer updated state to", new_state) - - def wait_for_negotiation_states(self, states): - ret = None - with self.state_cond: - while self.state not in states: - self.state_cond.wait() - print ("observer waited for", states) - ret = self.state - return ret + def _on_data_channel(self, element, channel): + observer = WebRTCBinDataChannelObserver(channel, channel.props.label, 'remote') + self.add_channel(observer) def _update_negotiation_from_description_state(self, desc): new_state = None @@ -101,6 +85,7 @@ class WebRTCBinObserver(object): new_state = NegotiationState.ANSWER_SET assert new_state is not None self._update_negotiation_state(new_state) + return new_state def _deepcopy_session_description(self, desc): # XXX: passing 'offer' to both a promise and an action signal without @@ -115,11 +100,10 @@ class WebRTCBinObserver(object): offer = reply['offer'] new_offer = self._deepcopy_session_description(offer) - promise = Gst.Promise.new_with_change_func(self._on_local_description_set, new_offer) + promise = Gst.Promise.new_with_change_func(self._on_description_set, new_offer) new_offer = self._deepcopy_session_description(offer) self.element.emit('set-local-description', new_offer, promise) - self.on_offer_created.fire(offer) def _on_answer_created(self, promise, element): @@ -128,11 +112,10 @@ class WebRTCBinObserver(object): offer = reply['answer'] new_offer = self._deepcopy_session_description(offer) - promise = Gst.Promise.new_with_change_func(self._on_local_description_set, new_offer) + promise = Gst.Promise.new_with_change_func(self._on_description_set, new_offer) new_offer = self._deepcopy_session_description(offer) self.element.emit('set-local-description', new_offer, promise) - self.on_answer_created.fire(offer) def create_offer(self, options=None): @@ -144,13 +127,24 @@ class WebRTCBinObserver(object): self.element.emit('create-answer', options, promise) def set_remote_description(self, desc): - promise = Gst.Promise.new_with_change_func(self._on_remote_description_set, desc) + promise = Gst.Promise.new_with_change_func(self._on_description_set, desc) self.element.emit('set-remote-description', desc, promise) def add_ice_candidate(self, mline, candidate): self.element.emit('add-ice-candidate', mline, candidate) + def add_data_channel(self, ident): + channel = self.element.emit('create-data-channel', ident, None) + observer = WebRTCBinDataChannelObserver(channel, ident, 'local') + self.add_channel(observer) + + def wait_for_negotiation_needed(self, generation): + self._negotiation_needed_observer.wait_for ((generation,)) + class WebRTCStream(object): + """ + An stream attached to a webrtcbin element + """ def __init__(self): self.bin = None @@ -189,6 +183,10 @@ class WebRTCStream(object): self.bin.sync_state_with_parent() class WebRTCClient(WebRTCBinObserver): + """ + Client for performing webrtc operations. Controls the pipeline that + contains a webrtcbin element. + """ def __init__(self): self.pipeline = Gst.Pipeline(None) self.webrtcbin = Gst.ElementFactory.make("webrtcbin") @@ -210,3 +208,42 @@ class WebRTCClient(WebRTCBinObserver): stream.set_description(desc) stream.add_and_link_to (self.pipeline, self.webrtcbin, pad) self._streams.append(stream) + + def set_options (self, opts): + if opts.has_field("local-bundle-policy"): + self.webrtcbin.props.bundle_policy = opts["local-bundle-policy"] + + +class WebRTCBinDataChannelObserver(DataChannelObserver): + """ + Data channel observer for a webrtcbin data channel. + """ + def __init__(self, target, ident, location): + super().__init__(ident, location) + self.target = target + self.signal_handlers = [] + self.signal_handlers.append(target.connect("on-open", self._on_open)) + self.signal_handlers.append(target.connect("on-close", self._on_close)) + self.signal_handlers.append(target.connect("on-error", self._on_error)) + self.signal_handlers.append(target.connect("on-message-data", self._on_message_data)) + self.signal_handlers.append(target.connect("on-message-string", self._on_message_string)) + self.signal_handlers.append(target.connect("on-buffered-amount-low", self._on_buffered_amount_low)) + + def _on_open(self, channel): + self._update_state (DataChannelState.OPEN) + def _on_close(self, channel): + self._update_state (DataChannelState.CLOSED) + def _on_error(self, channel): + self._update_state (DataChannelState.ERROR) + def _on_message_data(self, channel, data): + self.data.append(msg) + def _on_message_string(self, channel, msg): + self.got_message (msg) + def _on_buffered_amount_low(self, channel): + pass + + def close(self): + self.target.emit('close') + + def send_string (self, msg): + self.target.emit('send-string', msg) diff --git a/webrtc/check/validate/enums.py b/webrtc/check/validate/enums.py index 14bc31a49d..a23d2c9129 100644 --- a/webrtc/check/validate/enums.py +++ b/webrtc/check/validate/enums.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,22 +15,57 @@ # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301, USA. -class SignallingState(object): +from enum import Enum, unique + +@unique +class SignallingState(Enum): + """ + State of the signalling protocol. + """ NEW = "new" # no connection has been made + ERROR = "error" # an error was thrown. overrides all others OPEN = "open" # websocket connection is open - ERROR = "error" # and error was thrown. overrides all others HELLO = "hello" # hello was sent and received SESSION = "session" # session setup was sent and received -class NegotiationState(object): - NEW = "new" - ERROR = "error" - NEGOTIATION_NEEDED = "negotiation-needed" - OFFER_CREATED = "offer-created" - ANSWER_CREATED = "answer-created" - OFFER_SET = "offer-set" - ANSWER_SET = "answer-set" +@unique +class NegotiationState(Enum): + """ + State of the webrtc negotiation. Both peers have separate states and are + tracked separately. + """ + NEW = "new" # No negotiation has been performed + ERROR = "error" # an error occured + OFFER_CREATED = "offer-created" # offer was created + ANSWER_CREATED = "answer-created" # answer was created + OFFER_SET = "offer-set" # offer has been set + ANSWER_SET = "answer-set" # answer has been set -class RemoteState(object): - ERROR = "error" - REMOTE_STREAM_RECEIVED = "remote-stream-received" +@unique +class DataChannelState(Enum): + """ + State of a data channel. Each data channel is tracked individually + """ + NEW = "new" # data channel created but not connected + OPEN = "open" # data channel is open, data can flow + CLOSED = "closed" # data channel is closed, sending data will fail + ERROR = "error" # data channel encountered an error + +@unique +class Actions(Enum): + """ + Action names that we implement. Each name is the structure name for each + action as stored in the scenario file. + """ + CREATE_OFFER = "create-offer" # create an offer and send it to the peer + CREATE_ANSWER = "create-answer" # create an answer and send it to the peer + WAIT_FOR_NEGOTIATION_STATE = "wait-for-negotiation-state" # wait for the @NegotiationState to reach a certain value + ADD_STREAM = "add-stream" # add a stream to send to the peer. local only + ADD_DATA_CHANNEL = "add-data-channel" # add a stream to send to the peer. local only + WAIT_FOR_DATA_CHANNEL = "wait-for-data-channel" # wait for a data channel to appear + WAIT_FOR_DATA_CHANNEL_STATE = "wait-for-data-channel-state" # wait for a data channel to have a certain state + SEND_DATA_CHANNEL_STRING = "send-data-channel-string" # send a string over the data channel + WAIT_FOR_DATA_CHANNEL_STRING = "wait-for-data-channel-string" # wait for a string on the data channel + CLOSE_DATA_CHANNEL = "close-data-channel" # close a data channel + WAIT_FOR_NEGOTIATION_NEEDED = "wait-for-negotiation-needed" # wait for negotiation needed to fire + SET_WEBRTC_OPTIONS = "set-webrtc-options" # set some options diff --git a/webrtc/check/validate/observer.py b/webrtc/check/validate/observer.py index b4f6be512e..d60785865f 100644 --- a/webrtc/check/validate/observer.py +++ b/webrtc/check/validate/observer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,7 +15,17 @@ # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301, USA. +import logging +import threading + +from enums import NegotiationState, DataChannelState + +l = logging.getLogger(__name__) + class Signal(object): + """ + A class for callback-based signal handlers. + """ def __init__(self, cont_func=None, accum_func=None): self._handlers = [] if not cont_func: @@ -41,3 +51,119 @@ class Signal(object): if not self.cont_func(ret): break return ret + + +class StateObserver(object): + """ + Observe some state. Allows waiting for specific states to occur and + notifying waiters of updated values. Will hold previous states to ensure + @update cannot change the state before @wait_for can look at the state. + """ + def __init__(self, target, attr_name, cond): + self.target = target + self.attr_name = attr_name + self.cond = cond + # track previous states of the value so that the notification still + # occurs even if the field has moved on to another state + self.previous_states = [] + + def wait_for(self, states): + ret = None + with self.cond: + state = getattr (self.target, self.attr_name) + l.debug (str(self.target) + " \'" + self.attr_name + + "\' waiting for " + str(states)) + while True: + l.debug(str(self.target) + " \'" + self.attr_name + + "\' previous states: " + str(self.previous_states)) + for i, s in enumerate (self.previous_states): + if s in states: + l.debug(str(self.target) + " \'" + self.attr_name + + "\' " + str(s) + " found at position " + + str(i) + " of " + str(self.previous_states)) + self.previous_states = self.previous_states[i:] + return s + self.cond.wait() + + def update (self, new_state): + with self.cond: + self.previous_states += [new_state] + setattr(self.target, self.attr_name, new_state) + self.cond.notify_all() + l.debug (str(self.target) + " updated \'" + self.attr_name + "\' to " + str(new_state)) + + +class WebRTCObserver(object): + """ + Base webrtc observer class. Stores a lot of duplicated functionality + between the local and remove peer implementations. + """ + def __init__(self): + self.state = NegotiationState.NEW + self._state_observer = StateObserver(self, "state", threading.Condition()) + self.on_offer_created = Signal() + self.on_answer_created = Signal() + self.on_offer_set = Signal() + self.on_answer_set = Signal() + self.on_data_channel = Signal() + self.data_channels = [] + self._xxxxxxxdata_channel_ids = None + self._data_channels_observer = StateObserver(self, "_xxxxxxxdata_channel_ids", threading.Condition()) + + def _update_negotiation_state(self, new_state): + self._state_observer.update (new_state) + + def wait_for_negotiation_states(self, states): + return self._state_observer.wait_for (states) + + def find_channel (self, ident): + for c in self.data_channels: + if c.ident == ident: + return c + + def add_channel (self, channel): + l.debug('adding channel ' + str (channel) + ' with name ' + str(channel.ident)) + self.data_channels.append (channel) + self._data_channels_observer.update (channel.ident) + self.on_data_channel.fire(channel) + + def wait_for_data_channel(self, ident): + return self._data_channels_observer.wait_for (ident) + + def create_offer(self, options): + raise NotImplementedError() + + def add_data_channel(self, ident): + raise NotImplementedError() + + +class DataChannelObserver(object): + """ + Base webrtc data channelobserver class. Stores a lot of duplicated + functionality between the local and remove peer implementations. + """ + def __init__(self, ident, location): + self.state = DataChannelState.NEW + self._state_observer = StateObserver(self, "state", threading.Condition()) + self.ident = ident + self.location = location + self.message = None + self._message_observer = StateObserver(self, "message", threading.Condition()) + + def _update_state(self, new_state): + self._state_observer.update (new_state) + + def wait_for_states(self, states): + return self._state_observer.wait_for (states) + + def wait_for_message (self, msg): + return self._message_observer.wait_for (msg) + + def got_message(self, msg): + self._message_observer.update (msg) + + def close (self): + raise NotImplementedError() + + def send_string (self, msg): + raise NotImplementedError() diff --git a/webrtc/check/validate/scenarios/bundle_local_balanced_remote_balanced/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_balanced/bundle_policy.scenario new file mode 100644 index 0000000000..b3220c4b66 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_balanced/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=balanced, remote_bundle_policy=balanced diff --git a/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_bundle/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_bundle/bundle_policy.scenario new file mode 100644 index 0000000000..fd296161ca --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_bundle/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=balanced, remote_bundle_policy=max-bundle diff --git a/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_compat/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_compat/bundle_policy.scenario new file mode 100644 index 0000000000..d770994549 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_balanced_remote_max_compat/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=balanced, remote_bundle_policy=max-compat diff --git a/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_balanced/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_balanced/bundle_policy.scenario new file mode 100644 index 0000000000..34c2946e13 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_balanced/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-bundle, remote_bundle_policy=balanced diff --git a/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_bundle/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_bundle/bundle_policy.scenario new file mode 100644 index 0000000000..6504adf559 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_bundle/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-bundle, remote_bundle_policy=max-bundle diff --git a/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_compat/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_compat/bundle_policy.scenario new file mode 100644 index 0000000000..cbe82f40ec --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_bundle_remote_max_compat/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-bundle, remote_bundle_policy=max-compat diff --git a/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_balanced/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_balanced/bundle_policy.scenario new file mode 100644 index 0000000000..30bd7d16a4 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_balanced/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-compat, remote_bundle_policy=balanced diff --git a/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_bundle/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_bundle/bundle_policy.scenario new file mode 100644 index 0000000000..2b564b9711 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_bundle/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-compat, remote_bundle_policy=max-bundle diff --git a/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_compat/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_compat/bundle_policy.scenario new file mode 100644 index 0000000000..34508fab8d --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_max_compat_remote_max_compat/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=max-compat, remote_bundle_policy=max-compat diff --git a/webrtc/check/validate/scenarios/bundle_local_none_remote_balanced/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_none_remote_balanced/bundle_policy.scenario new file mode 100644 index 0000000000..57d208ae5d --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_none_remote_balanced/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=none, remote_bundle_policy=balanced diff --git a/webrtc/check/validate/scenarios/bundle_local_none_remote_max_bundle/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_none_remote_max_bundle/bundle_policy.scenario new file mode 100644 index 0000000000..adee196bf4 --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_none_remote_max_bundle/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=none, remote_bundle_policy=max-bundle diff --git a/webrtc/check/validate/scenarios/bundle_local_none_remote_max_compat/bundle_policy.scenario b/webrtc/check/validate/scenarios/bundle_local_none_remote_max_compat/bundle_policy.scenario new file mode 100644 index 0000000000..370881547d --- /dev/null +++ b/webrtc/check/validate/scenarios/bundle_local_none_remote_max_compat/bundle_policy.scenario @@ -0,0 +1 @@ +set-vars, local_bundle_policy=none, remote_bundle_policy=max-compat diff --git a/webrtc/check/validate/scenarios/local_initiates_negotiation/negotiation_initiator.scenario b/webrtc/check/validate/scenarios/local_initiates_negotiation/negotiation_initiator.scenario new file mode 100644 index 0000000000..022d2357fd --- /dev/null +++ b/webrtc/check/validate/scenarios/local_initiates_negotiation/negotiation_initiator.scenario @@ -0,0 +1 @@ +set-vars, negotiation_initiator=local, negotiation_responder=remote diff --git a/webrtc/check/validate/scenarios/offer_answer.scenario b/webrtc/check/validate/scenarios/offer_answer.scenario index 559f65fad7..4e797bd2e8 100644 --- a/webrtc/check/validate/scenarios/offer_answer.scenario +++ b/webrtc/check/validate/scenarios/offer_answer.scenario @@ -1,3 +1,15 @@ -description, summary="Produce an offer" -create-offer; -wait-for-negotiation-state, state="answer-set" +description, summary="Produce an offer and negotiate it with the peer" +include,location=negotiation_initiator.scenario +include,location=bundle_policy.scenario + +set-webrtc-options, local-bundle-policy="$(local_bundle_policy)", remote-bundle-policy="$(remote_bundle_policy)" + +create-offer, which="$(negotiation_initiator)"; +# all of these waits are technically unnecessary and only the last is needed +wait-for-negotiation-state, which="$(negotiation_initiator)", state="offer-created" +wait-for-negotiation-state, which="$(negotiation_initiator)", state="offer-set" +wait-for-negotiation-state, which="$(negotiation_responder)", state="offer-set" +create-answer, which="$(negotiation_responder)"; +wait-for-negotiation-state, which="$(negotiation_responder)", state="answer-created" +wait-for-negotiation-state, which="$(negotiation_responder)", state="answer-set" +wait-for-negotiation-state, which="$(negotiation_initiator)", state="answer-set" diff --git a/webrtc/check/validate/scenarios/open_data_channel.scenario b/webrtc/check/validate/scenarios/open_data_channel.scenario new file mode 100644 index 0000000000..8c1bc391b1 --- /dev/null +++ b/webrtc/check/validate/scenarios/open_data_channel.scenario @@ -0,0 +1,23 @@ +description, summary="Open a data channel" +include,location=negotiation_initiator.scenario +include,location=bundle_policy.scenario + +set-webrtc-options, local-bundle-policy="$(local_bundle_policy)", remote-bundle-policy="$(remote_bundle_policy)" + +# add the channel on the initiator so that datachannel is added to the sdp +add-data-channel, which="$(negotiation_initiator)", id="gstreamer"; + +# negotiate +create-offer, which="$(negotiation_initiator)"; +wait-for-negotiation-state, which="$(negotiation_responder)", state="offer-set" +create-answer, which="$(negotiation_responder)"; +wait-for-negotiation-state, which="$(negotiation_initiator)", state="answer-set" + +# ensure data channel is created +wait-for-data-channel, which="$(negotiation_responder)", id="gstreamer"; +wait-for-data-channel, which="$(negotiation_initiator)", id="gstreamer"; +wait-for-data-channel-state, which="$(negotiation_initiator)", id="gstreamer", state="open"; + +# only the browser closing works at the moment +close-data-channel, which="remote", id="gstreamer" +wait-for-data-channel-state, which="local", id="gstreamer", state="closed"; diff --git a/webrtc/check/validate/scenarios/remote_initiates_negotiation/negotiation_initiator.scenario b/webrtc/check/validate/scenarios/remote_initiates_negotiation/negotiation_initiator.scenario new file mode 100644 index 0000000000..07ba3bbdfe --- /dev/null +++ b/webrtc/check/validate/scenarios/remote_initiates_negotiation/negotiation_initiator.scenario @@ -0,0 +1 @@ +set-vars, negotiation_initiator=remote, negotiation_responder=local diff --git a/webrtc/check/validate/scenarios/send_data_channel_string.scenario b/webrtc/check/validate/scenarios/send_data_channel_string.scenario new file mode 100644 index 0000000000..5d247baee1 --- /dev/null +++ b/webrtc/check/validate/scenarios/send_data_channel_string.scenario @@ -0,0 +1,21 @@ +description, summary="Send data over a data channel" +include,location=negotiation_initiator.scenario +include,location=bundle_policy.scenario + +set-webrtc-options, local-bundle-policy="$(local_bundle_policy)", remote-bundle-policy="$(remote_bundle_policy)" + +add-data-channel, which="$(negotiation_initiator)", id="gstreamer"; + +create-offer, which="$(negotiation_initiator)"; +wait-for-negotiation-state, which="$(negotiation_responder)", state="offer-set" +create-answer, which="$(negotiation_responder)"; +wait-for-negotiation-state, which="$(negotiation_initiator)", state="answer-set" + +# wait for the data channel to appear +wait-for-data-channel, which="$(negotiation_initiator)", id="gstreamer"; +wait-for-data-channel, which="$(negotiation_responder)", id="gstreamer"; +wait-for-data-channel-state, which="$(negotiation_initiator)", id="gstreamer", state="open"; + +# send something +send-data-channel-string, which="local", id="gstreamer", msg="some data"; +wait-for-data-channel-string, which="remote", id="gstreamer", msg="some data"; diff --git a/webrtc/check/validate/scenarios/vp8_send_stream.scenario b/webrtc/check/validate/scenarios/vp8_send_stream.scenario index 2ec15a5391..308148886f 100644 --- a/webrtc/check/validate/scenarios/vp8_send_stream.scenario +++ b/webrtc/check/validate/scenarios/vp8_send_stream.scenario @@ -1,5 +1,15 @@ description, summary="Send a VP8 stream", handles-state=true +include,location=negotiation_initiator.scenario +include,location=bundle_policy.scenario + +set-webrtc-options, local-bundle-policy="$(local_bundle_policy)", remote-bundle-policy="$(remote_bundle_policy)" + add-stream, pipeline="videotestsrc is-live=1 ! vp8enc ! rtpvp8pay ! queue" set-state, state="playing"; -create-offer; -wait-for-negotiation-state, state="answer-set" +wait-for-negotiation-needed, generation=1; + +# negotiate +create-offer, which="$(negotiation_initiator)"; +wait-for-negotiation-state, which="$(negotiation_responder)", state="offer-set" +create-answer, which="$(negotiation_responder)"; +wait-for-negotiation-state, which="$(negotiation_initiator)", state="answer-set" diff --git a/webrtc/check/validate/signalling.py b/webrtc/check/validate/signalling.py index 2a7d9388b8..2e6d6748cc 100644 --- a/webrtc/check/validate/signalling.py +++ b/webrtc/check/validate/signalling.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2018, Matthew Waters +# Copyright (c) 2020, Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -24,11 +22,17 @@ import os import sys import threading import json +import logging -from observer import Signal -from enums import SignallingState, RemoteState +from observer import Signal, StateObserver, WebRTCObserver, DataChannelObserver +from enums import SignallingState, NegotiationState, DataChannelState + +l = logging.getLogger(__name__) class AsyncIOThread(threading.Thread): + """ + Run an asyncio loop in another thread. + """ def __init__ (self, loop): threading.Thread.__init__(self) self.loop = loop @@ -40,16 +44,24 @@ class AsyncIOThread(threading.Thread): def stop_thread(self): self.loop.call_soon_threadsafe(self.loop.stop) + class SignallingClientThread(object): + """ + Connect to a signalling server + """ def __init__(self, server): + # server string to connect to. Passed directly to websockets.connect() self.server = server + # fired after we have connected to the signalling server self.wss_connected = Signal() + # fired every time we receive a message from the signalling server self.message = Signal() self._init_async() def _init_async(self): + self._running = False self.conn = None self._loop = asyncio.new_event_loop() @@ -59,23 +71,31 @@ class SignallingClientThread(object): self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(self._a_loop())) async def _a_connect(self): + # connect to the signalling server assert not self.conn sslctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) self.conn = await websockets.connect(self.server, ssl=sslctx) async def _a_loop(self): + self._running = True + l.info('loop started') await self._a_connect() self.wss_connected.fire() assert self.conn async for message in self.conn: self.message.fire(message) + l.info('loop exited') def send(self, data): + # send some information to the peer async def _a_send(): await self.conn.send(data) self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_send())) def stop(self): + if self._running == False: + return + cond = threading.Condition() # asyncio, why you so complicated to stop ? @@ -87,64 +107,70 @@ class SignallingClientThread(object): to_wait = [t for t in tasks if not t.done()] if to_wait: + l.info('waiting for ' + str(to_wait)) done, pending = await asyncio.wait(to_wait) with cond: + l.error('notifying cond') cond.notify() + self._running = False - self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_stop())) with cond: - cond.wait() + self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_stop())) + l.error('cond waiting') + cond.wait() + l.error('cond waited') self._thread.stop_thread() self._thread.join() + l.error('thread joined') + class WebRTCSignallingClient(SignallingClientThread): + """ + Signalling client implementation. Deals wit session management over the + signalling protocol. Sends and receives from a peer. + """ def __init__(self, server, id_): super().__init__(server) self.wss_connected.connect(self._on_connection) self.message.connect(self._on_message) self.state = SignallingState.NEW - self._state_cond = threading.Condition() + self._state_observer = StateObserver(self, "state", threading.Condition()) self.id = id_ self._peerid = None - # override that base class + # fired when the hello has been received self.connected = Signal() + # fired when the signalling server responds that the session creation is ok self.session_created = Signal() + # fired on an error self.error = Signal() + # fired when the peer receives some json data self.have_json = Signal() - def wait_for_states(self, states): - ret = None - with self._state_cond: - while self.state not in states: - self._state_cond.wait() - ret = self.state - return ret + def _update_state(self, new_state): + self._state_observer.update (new_state) - def _update_state(self, state): - with self._state_cond: - if self.state is not SignallingState.ERROR: - self.state = state - self._state_cond.notify_all() + def wait_for_states(self, states): + return self._state_observer.wait_for (states) def hello(self): self.send('HELLO ' + str(self.id)) + l.info("sent HELLO") self.wait_for_states([SignallingState.HELLO]) - print("signalling-client sent HELLO") def create_session(self, peerid): self._peerid = peerid self.send('SESSION {}'.format(self._peerid)) - self.wait_for_states([SignallingState.SESSION, SignallingState.ERROR]) - print("signalling-client sent SESSION") + l.info("sent SESSION") + self.wait_for_states([SignallingState.SESSION]) def _on_connection(self): self._update_state (SignallingState.OPEN) def _on_message(self, message): - print("signalling-client received", message) + l.debug("received: " + message) if message == 'HELLO': self._update_state (SignallingState.HELLO) self.connected.fire() @@ -159,3 +185,82 @@ class WebRTCSignallingClient(SignallingClientThread): self.have_json.fire(msg) return False + +class RemoteWebRTCObserver(WebRTCObserver): + """ + Use information sent over the signalling channel to construct the current + state of a remote peer. Allow performing actions by sending requests over + the signalling channel. + """ + def __init__(self, signalling): + super().__init__() + self.signalling = signalling + + def on_json(msg): + if 'STATE' in msg: + state = NegotiationState (msg['STATE']) + self._update_negotiation_state(state) + if state == NegotiationState.OFFER_CREATED: + self.on_offer_created.fire(msg['description']) + elif state == NegotiationState.ANSWER_CREATED: + self.on_answer_created.fire(msg['description']) + elif state == NegotiationState.OFFER_SET: + self.on_offer_set.fire (msg['description']) + elif state == NegotiationState.ANSWER_SET: + self.on_answer_set.fire (msg['description']) + elif 'DATA-NEW' in msg: + new = msg['DATA-NEW'] + observer = RemoteDataChannelObserver(new['id'], new['location'], self) + self.add_channel (observer) + elif 'DATA-STATE' in msg: + ident = msg['id'] + channel = self.find_channel(ident) + channel._update_state (DataChannelState(msg['DATA-STATE'])) + elif 'DATA-MSG' in msg: + ident = msg['id'] + channel = self.find_channel(ident) + channel.got_message(msg['DATA-MSG']) + self.signalling.have_json.connect (on_json) + + def add_data_channel (self, ident): + msg = json.dumps({'DATA_CREATE': {'id': ident}}) + self.signalling.send (msg) + + def create_offer (self): + msg = json.dumps({'CREATE_OFFER': ""}) + self.signalling.send (msg) + + def create_answer (self): + msg = json.dumps({'CREATE_ANSWER': ""}) + self.signalling.send (msg) + + def set_title (self, title): + # entirely for debugging purposes + msg = json.dumps({'SET_TITLE': title}) + self.signalling.send (msg) + + def set_options (self, opts): + options = {} + if opts.has_field("remote-bundle-policy"): + options["bundlePolicy"] = opts["remote-bundle-policy"] + msg = json.dumps({'OPTIONS' : options}) + self.signalling.send (msg) + + +class RemoteDataChannelObserver(DataChannelObserver): + """ + Use information sent over the signalling channel to construct the current + state of a remote peer's data channel. Allow performing actions by sending + requests over the signalling channel. + """ + def __init__(self, ident, location, webrtc): + super().__init__(ident, location) + self.webrtc = webrtc + + def send_string(self, msg): + msg = json.dumps({'DATA_SEND_MSG': {'msg' : msg, 'id': self.ident}}) + self.webrtc.signalling.send (msg) + + def close (self): + msg = json.dumps({'DATA_CLOSE': {'id': self.ident}}) + self.webrtc.signalling.send (msg) diff --git a/webrtc/check/validate/testsuites/webrtc.py b/webrtc/check/validate/testsuites/webrtc.py index c078e5d314..3101098202 100644 --- a/webrtc/check/validate/testsuites/webrtc.py +++ b/webrtc/check/validate/testsuites/webrtc.py @@ -1,6 +1,4 @@ -#*- vi:si:et:sw=4:sts=4:ts=4:syntax=python -# -# Copyright (c) 2018 Matthew Waters +# Copyright (c) 2020 Matthew Waters # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -25,12 +23,9 @@ import os TEST_MANAGER = "webrtc" -BLACKLIST = [ - ] def setup_tests(test_manager, options): print("Setting up webrtc tests") -# test_manager.set_default_blacklist(BLACKLIST) return True diff --git a/webrtc/check/validate/web/single_stream.html b/webrtc/check/validate/web/single_stream.html index 9a95fb7491..218b200ed0 100644 --- a/webrtc/check/validate/web/single_stream.html +++ b/webrtc/check/validate/web/single_stream.html @@ -27,9 +27,5 @@
Our id is unknown

-
-
getUserMedia constraints being used:
-
-
diff --git a/webrtc/check/validate/web/webrtc.js b/webrtc/check/validate/web/webrtc.js index eb7e00f5e0..2a0d5358d1 100644 --- a/webrtc/check/validate/web/webrtc.js +++ b/webrtc/check/validate/web/webrtc.js @@ -14,13 +14,12 @@ var ws_port; var default_peer_id; // Override with your own STUN servers if you want var rtc_configuration = {iceServers: [{urls: "stun:stun.services.mozilla.com"}, - {urls: "stun:stun.l.google.com:19302"}]}; -// The default constraints that will be attempted. Can be overriden by the user. -var default_constraints = {video: true, audio: true}; + {urls: "stun:stun.l.google.com:19302"},]}; +var default_constraints = {video: true, audio: false}; var connect_attempts = 0; var peer_connection; -var send_channel; +var channels = [] var ws_conn; // Promise for local stream after constraints are approved by the user var local_stream_promise; @@ -56,7 +55,7 @@ function setError(text) { var span = document.getElementById("status") span.textContent = text; span.classList.add('error'); - ws_conn.send(JSON.stringify({'STATE': 'error'})) + ws_conn.send(JSON.stringify({'STATE': 'error', 'msg' : text})) } function resetVideo() { @@ -75,27 +74,40 @@ function resetVideo() { videoElement.load(); } +function updateRemoteStateFromSetSDPJson(sdp) { + if (sdp.type == "offer") + ws_conn.send(JSON.stringify({'STATE': 'offer-set', 'description' : sdp})) + else if (sdp.type == "answer") + ws_conn.send(JSON.stringify({'STATE': 'answer-set', 'description' : sdp})) + else + throw new Error ("Unknown SDP type!"); +} + +function updateRemoteStateFromGeneratedSDPJson(sdp) { + if (sdp.type == "offer") + ws_conn.send(JSON.stringify({'STATE': 'offer-created', 'description' : sdp})) + else if (sdp.type == "answer") + ws_conn.send(JSON.stringify({'STATE': 'answer-created', 'description' : sdp})) + else + throw new Error ("Unknown SDP type!"); +} + // SDP offer received from peer, set remote description and create an answer function onIncomingSDP(sdp) { peer_connection.setRemoteDescription(sdp).then(() => { - setStatus("Remote SDP set"); - if (sdp.type != "offer") - return; - setStatus("Got SDP offer"); - local_stream_promise.then((stream) => { - setStatus("Got local stream, creating answer"); - peer_connection.createAnswer() - .then(onLocalDescription).catch(setError); - }).catch(setError); + updateRemoteStateFromSetSDPJson(sdp) + setStatus("Set remote SDP", sdp.type); }).catch(setError); } // Local description was set, send it to peer function onLocalDescription(desc) { + updateRemoteStateFromGeneratedSDPJson(desc) console.log("Got local description: " + JSON.stringify(desc)); peer_connection.setLocalDescription(desc).then(function() { - setStatus("Sending SDP answer"); - sdp = {'sdp': peer_connection.localDescription} + updateRemoteStateFromSetSDPJson(desc) + sdp = {'sdp': desc} + setStatus("Sending SDP", sdp.type); ws_conn.send(JSON.stringify(sdp)); }); } @@ -103,9 +115,33 @@ function onLocalDescription(desc) { // ICE candidate received from peer, add it to the peer connection function onIncomingICE(ice) { var candidate = new RTCIceCandidate(ice); + console.log("adding candidate", candidate) peer_connection.addIceCandidate(candidate).catch(setError); } +function createOffer(offer) { + local_stream_promise.then((stream) => { + setStatus("Got local stream, creating offer"); + peer_connection.createOffer() + .then(onLocalDescription).catch(setError); + }).catch(setError) +} + +function createAnswer(offer) { + local_stream_promise.then((stream) => { + setStatus("Got local stream, creating answer"); + peer_connection.createAnswer() + .then(onLocalDescription).catch(setError); + }).catch(setError) +} + +function handleOptions(options) { + console.log ('received options', options); + if (options.bundlePolicy != null) { + rtc_configuration['bundlePolicy'] = options.bundlePolicy; + } +} + function onServerMessage(event) { console.log("Received " + event.data); switch (event.data) { @@ -129,14 +165,33 @@ function onServerMessage(event) { return; } + if (msg.SET_TITLE != null) { + // some debugging for tests that hang around + document.title = msg['SET_TITLE'] + return; + } else if (msg.OPTIONS != null) { + handleOptions(msg.OPTIONS); + return; + } + // Incoming JSON signals the beginning of a call if (!peer_connection) - createCall(msg); + createCall(); if (msg.sdp != null) { onIncomingSDP(msg.sdp); } else if (msg.ice != null) { onIncomingICE(msg.ice); + } else if (msg.CREATE_OFFER != null) { + createOffer(msg.CREATE_OFFER) + } else if (msg.CREATE_ANSWER != null) { + createAnswer(msg.CREATE_ANSWER) + } else if (msg.DATA_CREATE != null) { + addDataChannel(msg.DATA_CREATE.id) + } else if (msg.DATA_CLOSE != null) { + closeDataChannel(msg.DATA_CLOSE.id) + } else if (msg.DATA_SEND_MSG != null) { + sendDataChannelMessage(msg.DATA_SEND_MSG) } else { handleIncomingError("Unknown incoming JSON: " + msg); } @@ -151,6 +206,7 @@ function onServerClose(event) { peer_connection.close(); peer_connection = null; } + channels = [] // Reset after a second window.setTimeout(websocketServerConnect, 1000); @@ -164,14 +220,7 @@ function onServerError(event) { function getLocalStream() { var constraints; - var textarea = document.getElementById('constraints'); - try { - constraints = JSON.parse(textarea.value); - } catch (e) { - console.error(e); - setError('ERROR parsing constraints: ' + e.message + ', using default constraints'); - constraints = default_constraints; - } + constraints = default_constraints; console.log(JSON.stringify(constraints)); // Add local stream @@ -192,12 +241,7 @@ function websocketServerConnect() { var span = document.getElementById("status"); span.classList.remove('error'); span.textContent = ''; - // Populate constraints - var textarea = document.getElementById('constraints'); - if (textarea.value == '') - textarea.value = JSON.stringify(default_constraints); // Fetch the peer id to use - var url = new URL(window.location.href); peer_id = url.searchParams.get("id"); @@ -236,7 +280,6 @@ function onRemoteStreamAdded(event) { if (videoTracks.length > 0) { console.log('Incoming stream: ' + videoTracks.length + ' video tracks and ' + audioTracks.length + ' audio tracks'); getVideoElement().srcObject = event.stream; - ws_conn.send(JSON.stringify({'STATE': 'remote-stream-received'})) } else { handleIncomingError('Stream with unknown tracks added, resetting'); } @@ -246,53 +289,80 @@ function errorUserMediaHandler() { setError("Browser doesn't support getUserMedia!"); } -const handleDataChannelOpen = (event) =>{ - console.log("dataChannel.OnOpen", event); -}; - const handleDataChannelMessageReceived = (event) =>{ console.log("dataChannel.OnMessage:", event, event.data.type); - setStatus("Received data channel message"); - if (typeof event.data === 'string' || event.data instanceof String) { - console.log('Incoming string message: ' + event.data); - textarea = document.getElementById("text") - textarea.value = textarea.value + '\n' + event.data - } else { - console.log('Incoming data message'); - } - send_channel.send("Hi! (from browser)"); + ws_conn.send(JSON.stringify({'DATA-MSG' : event.data, 'id' : event.target.label})); +}; + +const handleDataChannelOpen = (event) =>{ + console.log("dataChannel.OnOpen", event); + ws_conn.send(JSON.stringify({'DATA-STATE' : 'open', 'id' : event.target.label})); }; const handleDataChannelError = (error) =>{ console.log("dataChannel.OnError:", error); + ws_conn.send(JSON.stringify({'DATA-STATE' : error, 'id' : event.target.label})); }; const handleDataChannelClose = (event) =>{ console.log("dataChannel.OnClose", event); + ws_conn.send(JSON.stringify({'DATA-STATE' : 'closed', 'id' : event.target.label})); }; function onDataChannel(event) { setStatus("Data channel created"); - let receiveChannel = event.channel; - receiveChannel.onopen = handleDataChannelOpen; - receiveChannel.onmessage = handleDataChannelMessageReceived; - receiveChannel.onerror = handleDataChannelError; - receiveChannel.onclose = handleDataChannelClose; + let channel = event.channel; + console.log('adding remote data channel with label', channel.label) + ws_conn.send(JSON.stringify({'DATA-NEW' : {'id' : channel.label, 'location' : 'remote'}})); + channel.onopen = handleDataChannelOpen; + channel.onmessage = handleDataChannelMessageReceived; + channel.onerror = handleDataChannelError; + channel.onclose = handleDataChannelClose; + channels.push(channel) } -function createCall(msg) { +function addDataChannel(label) { + channel = peer_connection.createDataChannel(label, null); + console.log('adding local data channel with label', label) + ws_conn.send(JSON.stringify({'DATA-NEW' : {'id' : label, 'location' : 'local'}})); + channel.onopen = handleDataChannelOpen; + channel.onmessage = handleDataChannelMessageReceived; + channel.onerror = handleDataChannelError; + channel.onclose = handleDataChannelClose; + channels.push(channel) +} + +function find_channel(label) { + console.log('find', label, 'in', channels) + for (var c in channels) { + if (channels[c].label === label) { + console.log('found', label, c) + return channels[c]; + } + } + return null; +} + +function closeDataChannel(label) { + channel = find_channel (label) + console.log('closing data channel with label', label) + channel.close() +} + +function sendDataChannelMessage(msg) { + channel = find_channel (msg.id) + console.log('sending on data channel', msg.id, 'message', msg.msg) + channel.send(msg.msg) +} + +function createCall() { // Reset connection attempts because we connected successfully connect_attempts = 0; - console.log('Creating RTCPeerConnection'); + console.log('Creating RTCPeerConnection with configuration', rtc_configuration); peer_connection = new RTCPeerConnection(rtc_configuration); - send_channel = peer_connection.createDataChannel('label', null); - send_channel.onopen = handleDataChannelOpen; - send_channel.onmessage = handleDataChannelMessageReceived; - send_channel.onerror = handleDataChannelError; - send_channel.onclose = handleDataChannelClose; peer_connection.ondatachannel = onDataChannel; peer_connection.onaddstream = onRemoteStreamAdded; /* Send our video/audio to the other peer */ @@ -302,18 +372,15 @@ function createCall(msg) { return stream; }).catch(setError); - if (!msg.sdp) { - console.log("WARNING: First message wasn't an SDP message!?"); - } - peer_connection.onicecandidate = (event) => { - // We have a candidate, send it to the remote party with the - // same uuid - if (event.candidate == null) { + // We have a candidate, send it to the remote party with the + // same uuid + if (event.candidate == null) { console.log("ICE Candidate was null, done"); return; - } - ws_conn.send(JSON.stringify({'ice': event.candidate})); + } + console.log("generated ICE Candidate", event.candidate); + ws_conn.send(JSON.stringify({'ice': event.candidate})); }; setStatus("Created peer connection for call, waiting for SDP"); diff --git a/webrtc/check/validate/webrtc_validate.py b/webrtc/check/validate/webrtc_validate.py index 6215a0a2e2..67f3aebf4e 100644 --- a/webrtc/check/validate/webrtc_validate.py +++ b/webrtc/check/validate/webrtc_validate.py @@ -21,12 +21,13 @@ import os import sys import argparse import json +import logging -from signalling import WebRTCSignallingClient -from actions import register_action_types, ActionObserver +from signalling import WebRTCSignallingClient, RemoteWebRTCObserver +from actions import ActionObserver from client import WebRTCClient from browser import Browser, create_driver -from enums import SignallingState, NegotiationState, RemoteState +from enums import SignallingState, NegotiationState, DataChannelState, Actions import gi gi.require_version("GLib", "2.0") @@ -40,14 +41,20 @@ from gi.repository import GstSdp gi.require_version("GstValidate", "1.0") from gi.repository import GstValidate +FORMAT = '%(asctime)-23s %(levelname)-7s %(thread)d %(name)-24s\t%(funcName)-24s %(message)s' +LEVEL = os.environ.get("LOGLEVEL", "DEBUG") +logging.basicConfig(level=LEVEL, format=FORMAT) +l = logging.getLogger(__name__) + class WebRTCApplication(object): - def __init__(self, server, id_, peerid, scenario_name, browser_name, html_source): + def __init__(self, server, id_, peerid, scenario_name, browser_name, html_source, test_name=None): self.server = server self.peerid = peerid self.html_source = html_source self.id = id_ self.scenario_name = scenario_name self.browser_name = browser_name + self.test_name = test_name def _init_validate(self, scenario_file): self.runner = GstValidate.Runner.new() @@ -61,24 +68,79 @@ class WebRTCApplication(object): self.client.pipeline.set_state(Gst.State.PLAYING) def _on_scenario_done(self, scenario): - self.quit() + l.error ('scenario done') + GLib.idle_add(self.quit) - def _connect_actions(self): - def create_offer(): - self.client.create_offer(None) - return GstValidate.ActionReturn.OK - self.actions.create_offer.connect(create_offer) + def _connect_actions(self, actions): + def on_action(atype, action): + """ + From a validate action, perform the action as required + """ + if atype == Actions.CREATE_OFFER: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + c.create_offer() + return GstValidate.ActionReturn.OK + elif atype == Actions.CREATE_ANSWER: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + c.create_answer() + return GstValidate.ActionReturn.OK + elif atype == Actions.WAIT_FOR_NEGOTIATION_STATE: + states = [NegotiationState(action.structure["state"]), NegotiationState.ERROR] + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + state = c.wait_for_negotiation_states(states) + return GstValidate.ActionReturn.OK if state != NegotiationState.ERROR else GstValidate.ActionReturn.ERROR + elif atype == Actions.ADD_STREAM: + self.client.add_stream(action.structure["pipeline"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.ADD_DATA_CHANNEL: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + c.add_data_channel(action.structure["id"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.SEND_DATA_CHANNEL_STRING: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + channel = c.find_channel (action.structure["id"]) + channel.send_string (action.structure["msg"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.WAIT_FOR_DATA_CHANNEL_STATE: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + states = [DataChannelState(action.structure["state"]), DataChannelState.ERROR] + channel = c.find_channel (action.structure["id"]) + state = channel.wait_for_states(states) + return GstValidate.ActionReturn.OK if state != DataChannelState.ERROR else GstValidate.ActionReturn.ERROR + elif atype == Actions.CLOSE_DATA_CHANNEL: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + channel = c.find_channel (action.structure["id"]) + channel.close() + return GstValidate.ActionReturn.OK + elif atype == Actions.WAIT_FOR_DATA_CHANNEL: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + state = c.wait_for_data_channel(action.structure["id"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.WAIT_FOR_DATA_CHANNEL_STRING: + assert action.structure["which"] in ("local", "remote") + c = self.client if action.structure["which"] == "local" else self.remote_client + channel = c.find_channel (action.structure["id"]) + channel.wait_for_message(action.structure["msg"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.WAIT_FOR_NEGOTIATION_NEEDED: + self.client.wait_for_negotiation_needed(action.structure["generation"]) + return GstValidate.ActionReturn.OK + elif atype == Actions.SET_WEBRTC_OPTIONS: + self.client.set_options (action.structure) + self.remote_client.set_options (action.structure) + return GstValidate.ActionReturn.OK + else: + assert "Not reached" == "" - def wait_for_negotiation_state(state): - states = [state, NegotiationState.ERROR] - state = self.client.wait_for_negotiation_states(states) - return GstValidate.ActionReturn.OK if state != RemoteState.ERROR else GstValidate.ActionReturn.ERROR - self.actions.wait_for_negotiation_state.connect(wait_for_negotiation_state) - - def add_stream(pipeline): - self.client.add_stream(pipeline) - return GstValidate.ActionReturn.OK - self.actions.add_stream.connect(add_stream) + actions.action.connect (on_action) def _connect_client_observer(self): def on_offer_created(offer): @@ -87,6 +149,12 @@ class WebRTCApplication(object): self.signalling.send(msg) self.client.on_offer_created.connect(on_offer_created) + def on_answer_created(answer): + text = answer.sdp.as_text() + msg = json.dumps({'sdp': {'type': 'answer', 'sdp': text}}) + self.signalling.send(msg) + self.client.on_answer_created.connect(on_answer_created) + def on_ice_candidate(mline, candidate): msg = json.dumps({'ice': {'sdpMLineIndex': str(mline), 'candidate' : candidate}}) self.signalling.send(msg) @@ -116,6 +184,7 @@ class WebRTCApplication(object): def error(msg): # errors are unexpected + l.error ('Unexpected error: ' + msg) GLib.idle_add(self.quit) GLib.idle_add(sys.exit, -20) self.signalling.error.connect(error) @@ -126,37 +195,50 @@ class WebRTCApplication(object): self.client = WebRTCClient() self._connect_client_observer() - self.actions = ActionObserver() - register_action_types(self.actions) - self._connect_actions() - self.signalling = WebRTCSignallingClient(self.server, self.id) + self.remote_client = RemoteWebRTCObserver (self.signalling) self._connect_signalling_observer() + + actions = ActionObserver() + actions.register_action_types() + self._connect_actions(actions) + + # wait for the signalling server to start up before creating the browser self.signalling.wait_for_states([SignallingState.OPEN]) self.signalling.hello() - self.browser = Browser(create_driver(self.browser_name), self.html_source) + self.browser = Browser(create_driver(self.browser_name)) + self.browser.open(self.html_source) browser_id = self.browser.get_peer_id () assert browser_id == self.peerid self.signalling.create_session(self.peerid) + test_name = self.test_name if self.test_name else self.scenario_name + self.remote_client.set_title (test_name) self._init_validate(self.scenario_name) - print("app initialized") def quit(self): # Stop signalling first so asyncio doesn't keep us alive on weird failures + l.info('quiting') self.signalling.stop() - self.browser.driver.quit() - self.client.stop() + l.info('signalling stopped') self.main_loop.quit() + l.info('main loop stopped') + self.client.stop() + l.info('client stopped') + self.browser.driver.quit() + l.info('browser exitted') def run(self): try: self._init() + l.info("app initialized") self.main_loop.run() + l.info("loop exited") except: + l.exception("Fatal error") self.quit() raise @@ -168,6 +250,7 @@ def parse_options(): parser.add_argument('--html-source', help='HTML page to open in the browser', default=None) parser.add_argument('--scenario', help='Scenario file to execute', default=None) parser.add_argument('--browser', help='Browser name to use', default=None) + parser.add_argument('--name', help='Name of the test', default=None) return parser.parse_args() def init(): @@ -196,7 +279,7 @@ def init(): def run(): args = init() - w = WebRTCApplication (args.server, args.id, args.peer_id, args.scenario, args.browser, args.html_source) + w = WebRTCApplication (args.server, args.id, args.peer_id, args.scenario, args.browser, args.html_source, test_name=args.name) return w.run() if __name__ == "__main__": From 3a86a37c03c9701061abe551ac1d9fb2e38dd610 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 1 May 2020 18:52:33 +1000 Subject: [PATCH 343/412] sendrecv: wait until the offer is set before creating answer Pragmatically, an answer cannot be created until the offer is created as the answer creation needs information from the offer. Practically, due to implementation details, the answer was always queued after the set of the offer and so the call flow did not matter. The current code also hid a bug in webrtcbin where ice candidates would be generated before the answer had been created which is against the JSEP specification. Change to the correct call flow for exemplary effect. --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index f2fef123cc..956b92b95a 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -517,7 +517,15 @@ on_answer_created (GstPromise * promise, gpointer user_data) } static void -on_offer_received (GstSDPMessage * sdp) +on_offer_set (GstPromise * promise, gpointer user_data) +{ + gst_promise_unref (promise); + promise = gst_promise_new_with_change_func (on_answer_created, NULL, NULL); + g_signal_emit_by_name (webrtc1, "create-answer", NULL, promise); +} + +static void +on_offer_received (GstSDPMessage *sdp) { GstWebRTCSessionDescription *offer = NULL; GstPromise *promise; @@ -527,15 +535,11 @@ on_offer_received (GstSDPMessage * sdp) /* 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); + promise = gst_promise_new_with_change_func (on_offer_set, NULL, NULL); + g_signal_emit_by_name (webrtc1, "set-remote-description", offer, + 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 */ From 7445fc492835a07fb719e6861f3b0502dce8aed6 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 1 May 2020 18:58:30 +1000 Subject: [PATCH 344/412] signalling/server: python 3.8 asyncio has it's own TimeoutError --- webrtc/signalling/simple_server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index ead3034a36..7da614bb05 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -15,8 +15,7 @@ import asyncio import websockets import argparse import http - -from concurrent.futures._base import TimeoutError +import concurrent class WebRTCSimpleServer(object): @@ -58,7 +57,7 @@ class WebRTCSimpleServer(object): while msg is None: try: msg = await asyncio.wait_for(ws.recv(), self.keepalive_timeout) - except TimeoutError: + except (asyncio.exceptions.TimeoutError, concurrent.futures._base.TimeoutError): print('Sending keepalive ping to {!r} in recv'.format(raddr)) await ws.ping() return msg From 8da83759861c018e9d6abd79b7a1eb6c55090231 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Thu, 19 Mar 2020 16:28:19 +1100 Subject: [PATCH 345/412] sendonly: Fix transceivers leak. Make sure to unref the transceivers array after use. --- webrtc/sendonly/webrtc-unidirectional-h264.c | 1 + 1 file changed, 1 insertion(+) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index bb9ca8e44b..4887c7eed2 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -196,6 +196,7 @@ create_receiver_entry (SoupWebsocketConnection * connection) g_assert (transceivers != NULL && transceivers->len > 0); trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + g_array_unref (transceivers); g_signal_connect (receiver_entry->webrtcbin, "on-negotiation-needed", G_CALLBACK (on_negotiation_needed_cb), (gpointer) receiver_entry); From 255fef3896221170c29c9820b869b9e7b24175bd Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 9 May 2020 19:09:26 +1000 Subject: [PATCH 346/412] webrtc-recvonly-h264: Add a recvonly standalone example. This example sets up a recvonly H.264 transceiver and receives H.264 from a peer, while sending bi-directional Opus audio. --- webrtc/sendonly/Makefile | 5 + webrtc/sendonly/webrtc-recvonly-h264.c | 702 +++++++++++++++++++++++++ 2 files changed, 707 insertions(+) create mode 100644 webrtc/sendonly/webrtc-recvonly-h264.c diff --git a/webrtc/sendonly/Makefile b/webrtc/sendonly/Makefile index ad588062d8..e153bc0379 100644 --- a/webrtc/sendonly/Makefile +++ b/webrtc/sendonly/Makefile @@ -2,5 +2,10 @@ CC := gcc LIBS := $(shell pkg-config --libs --cflags gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) CFLAGS := -O0 -ggdb -Wall -fno-omit-frame-pointer +all: webrtc-unidirectional-h264 webrtc-recvonly-h264 + webrtc-unidirectional-h264: webrtc-unidirectional-h264.c "$(CC)" $(CFLAGS) $^ $(LIBS) -o $@ + +webrtc-recvonly-h264: webrtc-recvonly-h264.c + "$(CC)" $(CFLAGS) $^ $(LIBS) -o $@ diff --git a/webrtc/sendonly/webrtc-recvonly-h264.c b/webrtc/sendonly/webrtc-recvonly-h264.c new file mode 100644 index 0000000000..9ba7fe4926 --- /dev/null +++ b/webrtc/sendonly/webrtc-recvonly-h264.c @@ -0,0 +1,702 @@ +#include +#include +#include +#include +#include + +#define GST_USE_UNSTABLE_API +#include + +#include +#include +#include + +/* This example is a standalone app which serves a web page + * and configures webrtcbin to receive an H.264 video feed, and to + * send+recv an Opus audio stream */ + +#define RTP_PAYLOAD_TYPE "96" +#define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=" + +#define SOUP_HTTP_PORT 57778 +#define STUN_SERVER "stun.l.google.com:19302" + + + +typedef struct _ReceiverEntry ReceiverEntry; + +ReceiverEntry *create_receiver_entry (SoupWebsocketConnection * connection); +void destroy_receiver_entry (gpointer receiver_entry_ptr); + +GstPadProbeReturn payloader_caps_event_probe_cb (GstPad * pad, + GstPadProbeInfo * info, gpointer user_data); + +void on_offer_created_cb (GstPromise * promise, gpointer user_data); +void on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data); +void on_ice_candidate_cb (GstElement * webrtcbin, guint mline_index, + gchar * candidate, gpointer user_data); + +void soup_websocket_message_cb (SoupWebsocketConnection * connection, + SoupWebsocketDataType data_type, GBytes * message, gpointer user_data); +void soup_websocket_closed_cb (SoupWebsocketConnection * connection, + gpointer user_data); + +void soup_http_handler (SoupServer * soup_server, SoupMessage * message, + const char *path, GHashTable * query, SoupClientContext * client_context, + gpointer user_data); +void soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, + SoupWebsocketConnection * connection, const char *path, + SoupClientContext * client_context, gpointer user_data); + +static gchar *get_string_from_json_object (JsonObject * object); + +gboolean exit_sighandler (gpointer user_data); + + + + +struct _ReceiverEntry +{ + SoupWebsocketConnection *connection; + + GstElement *pipeline; + GstElement *webrtcbin; +}; + + + +const gchar *html_source = " \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ +
\n \ + \n \ +
\n \ + \n \ + \n \ +"; + +static void +handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, + const char * sink_name) +{ + GstPad *qpad; + GstElement *q, *conv, *resample, *sink; + GstPadLinkReturn ret; + + g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); + + q = gst_element_factory_make ("queue", NULL); + g_assert_nonnull (q); + conv = gst_element_factory_make (convert_name, NULL); + g_assert_nonnull (conv); + sink = gst_element_factory_make (sink_name, NULL); + g_assert_nonnull (sink); + + if (g_strcmp0 (convert_name, "audioconvert") == 0) { + /* Might also need to resample, so add it just in case. + * Will be a no-op if it's not required. */ + resample = gst_element_factory_make ("audioresample", NULL); + g_assert_nonnull (resample); + gst_bin_add_many (GST_BIN (pipe), q, conv, resample, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (resample); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, resample, sink, NULL); + } else { + gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); + gst_element_sync_state_with_parent (q); + gst_element_sync_state_with_parent (conv); + gst_element_sync_state_with_parent (sink); + gst_element_link_many (q, conv, sink, NULL); + } + + qpad = gst_element_get_static_pad (q, "sink"); + + ret = gst_pad_link (pad, qpad); + g_assert_cmphex (ret, ==, GST_PAD_LINK_OK); +} + +static void +on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, + GstElement * pipe) +{ + GstCaps *caps; + const gchar *name; + + if (!gst_pad_has_current_caps (pad)) { + g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + GST_PAD_NAME (pad)); + return; + } + + caps = gst_pad_get_current_caps (pad); + name = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + + if (g_str_has_prefix (name, "video")) { + handle_media_stream (pad, pipe, "videoconvert", "autovideosink"); + } else if (g_str_has_prefix (name, "audio")) { + handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); + } else { + g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + } +} + +static void +on_incoming_stream (GstElement * webrtc, GstPad * pad, ReceiverEntry *receiver_entry) +{ + GstElement *decodebin; + GstPad *sinkpad; + + if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) + return; + + decodebin = gst_element_factory_make ("decodebin", NULL); + g_signal_connect (decodebin, "pad-added", + G_CALLBACK (on_incoming_decodebin_stream), receiver_entry->pipeline); + gst_bin_add (GST_BIN (receiver_entry->pipeline), decodebin); + gst_element_sync_state_with_parent (decodebin); + + sinkpad = gst_element_get_static_pad (decodebin, "sink"); + gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); +} + + +ReceiverEntry * +create_receiver_entry (SoupWebsocketConnection * connection) +{ + GError *error; + ReceiverEntry *receiver_entry; + GstCaps *video_caps; + GstWebRTCRTPTransceiver *trans = NULL; + + receiver_entry = g_slice_alloc0 (sizeof (ReceiverEntry)); + receiver_entry->connection = connection; + + g_object_ref (G_OBJECT (connection)); + + g_signal_connect (G_OBJECT (connection), "message", + G_CALLBACK (soup_websocket_message_cb), (gpointer) receiver_entry); + + error = NULL; + receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " + "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " + "queue ! " RTP_CAPS_OPUS "97 ! webrtcbin. " + , &error); + if (error != NULL) { + g_error ("Could not create WebRTC pipeline: %s\n", error->message); + g_error_free (error); + goto cleanup; + } + + receiver_entry->webrtcbin = + gst_bin_get_by_name (GST_BIN (receiver_entry->pipeline), "webrtcbin"); + g_assert (receiver_entry->webrtcbin != NULL); + + /* Incoming streams will be exposed via this signal */ + g_signal_connect (receiver_entry->webrtcbin, "pad-added", G_CALLBACK (on_incoming_stream), + receiver_entry); + +#if 0 + GstElement *rtpbin = gst_bin_get_by_name (GST_BIN (receiver_entry->webrtcbin), "rtpbin"); + g_object_set (rtpbin, "latency", 40, NULL); + gst_object_unref (rtpbin); +#endif + + // Create a 2nd transceiver for the receive only video stream + video_caps = gst_caps_from_string ("application/x-rtp,media=video,encoding-name=H264,payload=" RTP_PAYLOAD_TYPE ",clock-rate=90000,packetization-mode=(string)1, profile-level-id=(string)42c016"); + g_signal_emit_by_name (receiver_entry->webrtcbin, "add-transceiver", GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, video_caps, NULL, &trans); + gst_caps_unref (video_caps); + gst_object_unref (trans); + + g_signal_connect (receiver_entry->webrtcbin, "on-negotiation-needed", + G_CALLBACK (on_negotiation_needed_cb), (gpointer) receiver_entry); + + g_signal_connect (receiver_entry->webrtcbin, "on-ice-candidate", + G_CALLBACK (on_ice_candidate_cb), (gpointer) receiver_entry); + + gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING); + + return receiver_entry; + +cleanup: + destroy_receiver_entry ((gpointer) receiver_entry); + return NULL; +} + +void +destroy_receiver_entry (gpointer receiver_entry_ptr) +{ + ReceiverEntry *receiver_entry = (ReceiverEntry *) receiver_entry_ptr; + + g_assert (receiver_entry != NULL); + + if (receiver_entry->pipeline != NULL) { + gst_element_set_state (GST_ELEMENT (receiver_entry->pipeline), + GST_STATE_NULL); + + gst_object_unref (GST_OBJECT (receiver_entry->webrtcbin)); + gst_object_unref (GST_OBJECT (receiver_entry->pipeline)); + } + + if (receiver_entry->connection != NULL) + g_object_unref (G_OBJECT (receiver_entry->connection)); + + g_slice_free1 (sizeof (ReceiverEntry), receiver_entry); +} + + +void +on_offer_created_cb (GstPromise * promise, gpointer user_data) +{ + gchar *sdp_string; + gchar *json_string; + JsonObject *sdp_json; + JsonObject *sdp_data_json; + GstStructure const *reply; + GstPromise *local_desc_promise; + GstWebRTCSessionDescription *offer = NULL; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, + &offer, NULL); + gst_promise_unref (promise); + + local_desc_promise = gst_promise_new (); + g_signal_emit_by_name (receiver_entry->webrtcbin, "set-local-description", + offer, local_desc_promise); + gst_promise_interrupt (local_desc_promise); + gst_promise_unref (local_desc_promise); + + sdp_string = gst_sdp_message_as_text (offer->sdp); + g_print ("Negotiation offer created:\n%s\n", sdp_string); + + sdp_json = json_object_new (); + json_object_set_string_member (sdp_json, "type", "sdp"); + + sdp_data_json = json_object_new (); + json_object_set_string_member (sdp_data_json, "type", "offer"); + json_object_set_string_member (sdp_data_json, "sdp", sdp_string); + json_object_set_object_member (sdp_json, "data", sdp_data_json); + + json_string = get_string_from_json_object (sdp_json); + json_object_unref (sdp_json); + + soup_websocket_connection_send_text (receiver_entry->connection, json_string); + g_free (json_string); + g_free (sdp_string); + + gst_webrtc_session_description_free (offer); +} + + +void +on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data) +{ + GstPromise *promise; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + g_print ("Creating negotiation offer\n"); + + promise = gst_promise_new_with_change_func (on_offer_created_cb, + (gpointer) receiver_entry, NULL); + g_signal_emit_by_name (G_OBJECT (webrtcbin), "create-offer", NULL, promise); +} + + +void +on_ice_candidate_cb (G_GNUC_UNUSED GstElement * webrtcbin, guint mline_index, + gchar * candidate, gpointer user_data) +{ + JsonObject *ice_json; + JsonObject *ice_data_json; + gchar *json_string; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + ice_json = json_object_new (); + json_object_set_string_member (ice_json, "type", "ice"); + + ice_data_json = json_object_new (); + json_object_set_int_member (ice_data_json, "sdpMLineIndex", mline_index); + json_object_set_string_member (ice_data_json, "candidate", candidate); + json_object_set_object_member (ice_json, "data", ice_data_json); + + json_string = get_string_from_json_object (ice_json); + json_object_unref (ice_json); + + soup_websocket_connection_send_text (receiver_entry->connection, json_string); + g_free (json_string); +} + + +void +soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, + SoupWebsocketDataType data_type, GBytes * message, gpointer user_data) +{ + gsize size; + gchar *data; + gchar *data_string; + const gchar *type_string; + JsonNode *root_json; + JsonObject *root_json_object; + JsonObject *data_json_object; + JsonParser *json_parser = NULL; + ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; + + switch (data_type) { + case SOUP_WEBSOCKET_DATA_BINARY: + g_error ("Received unknown binary message, ignoring\n"); + g_bytes_unref (message); + return; + + case SOUP_WEBSOCKET_DATA_TEXT: + data = g_bytes_unref_to_data (message, &size); + /* Convert to NULL-terminated string */ + data_string = g_strndup (data, size); + g_free (data); + break; + + default: + g_assert_not_reached (); + } + + json_parser = json_parser_new (); + if (!json_parser_load_from_data (json_parser, data_string, -1, NULL)) + goto unknown_message; + + root_json = json_parser_get_root (json_parser); + if (!JSON_NODE_HOLDS_OBJECT (root_json)) + goto unknown_message; + + root_json_object = json_node_get_object (root_json); + + if (!json_object_has_member (root_json_object, "type")) { + g_error ("Received message without type field\n"); + goto cleanup; + } + type_string = json_object_get_string_member (root_json_object, "type"); + + if (!json_object_has_member (root_json_object, "data")) { + g_error ("Received message without data field\n"); + goto cleanup; + } + data_json_object = json_object_get_object_member (root_json_object, "data"); + + if (g_strcmp0 (type_string, "sdp") == 0) { + const gchar *sdp_type_string; + const gchar *sdp_string; + GstPromise *promise; + GstSDPMessage *sdp; + GstWebRTCSessionDescription *answer; + int ret; + + if (!json_object_has_member (data_json_object, "type")) { + g_error ("Received SDP message without type field\n"); + goto cleanup; + } + sdp_type_string = json_object_get_string_member (data_json_object, "type"); + + if (g_strcmp0 (sdp_type_string, "answer") != 0) { + g_error ("Expected SDP message type \"answer\", got \"%s\"\n", + sdp_type_string); + goto cleanup; + } + + if (!json_object_has_member (data_json_object, "sdp")) { + g_error ("Received SDP message without SDP string\n"); + goto cleanup; + } + sdp_string = json_object_get_string_member (data_json_object, "sdp"); + + g_print ("Received SDP:\n%s\n", sdp_string); + + ret = gst_sdp_message_new (&sdp); + g_assert_cmphex (ret, ==, GST_SDP_OK); + + ret = + gst_sdp_message_parse_buffer ((guint8 *) sdp_string, + strlen (sdp_string), sdp); + if (ret != GST_SDP_OK) { + g_error ("Could not parse SDP string\n"); + goto cleanup; + } + + answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, + sdp); + g_assert_nonnull (answer); + + promise = gst_promise_new (); + g_signal_emit_by_name (receiver_entry->webrtcbin, "set-remote-description", + answer, promise); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + gst_webrtc_session_description_free (answer); + } else if (g_strcmp0 (type_string, "ice") == 0) { + guint mline_index; + const gchar *candidate_string; + + if (!json_object_has_member (data_json_object, "sdpMLineIndex")) { + g_error ("Received ICE message without mline index\n"); + goto cleanup; + } + mline_index = + json_object_get_int_member (data_json_object, "sdpMLineIndex"); + + if (!json_object_has_member (data_json_object, "candidate")) { + g_error ("Received ICE message without ICE candidate string\n"); + goto cleanup; + } + candidate_string = json_object_get_string_member (data_json_object, + "candidate"); + + g_print ("Received ICE candidate with mline index %u; candidate: %s\n", + mline_index, candidate_string); + + g_signal_emit_by_name (receiver_entry->webrtcbin, "add-ice-candidate", + mline_index, candidate_string); + } else + goto unknown_message; + +cleanup: + if (json_parser != NULL) + g_object_unref (G_OBJECT (json_parser)); + g_free (data_string); + return; + +unknown_message: + g_error ("Unknown message \"%s\", ignoring", data_string); + goto cleanup; +} + + +void +soup_websocket_closed_cb (SoupWebsocketConnection * connection, + gpointer user_data) +{ + GHashTable *receiver_entry_table = (GHashTable *) user_data; + g_hash_table_remove (receiver_entry_table, connection); + g_print ("Closed websocket connection %p\n", (gpointer) connection); +} + + +void +soup_http_handler (G_GNUC_UNUSED SoupServer * soup_server, + SoupMessage * message, const char *path, G_GNUC_UNUSED GHashTable * query, + G_GNUC_UNUSED SoupClientContext * client_context, + G_GNUC_UNUSED gpointer user_data) +{ + SoupBuffer *soup_buffer; + + if ((g_strcmp0 (path, "/") != 0) && (g_strcmp0 (path, "/index.html") != 0)) { + soup_message_set_status (message, SOUP_STATUS_NOT_FOUND); + return; + } + + soup_buffer = + soup_buffer_new (SOUP_MEMORY_STATIC, html_source, strlen (html_source)); + + soup_message_headers_set_content_type (message->response_headers, "text/html", + NULL); + soup_message_body_append_buffer (message->response_body, soup_buffer); + soup_buffer_free (soup_buffer); + + soup_message_set_status (message, SOUP_STATUS_OK); +} + + +void +soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, + SoupWebsocketConnection * connection, G_GNUC_UNUSED const char *path, + G_GNUC_UNUSED SoupClientContext * client_context, gpointer user_data) +{ + ReceiverEntry *receiver_entry; + GHashTable *receiver_entry_table = (GHashTable *) user_data; + + g_print ("Processing new websocket connection %p", (gpointer) connection); + + g_signal_connect (G_OBJECT (connection), "closed", + G_CALLBACK (soup_websocket_closed_cb), (gpointer) receiver_entry_table); + + receiver_entry = create_receiver_entry (connection); + g_hash_table_replace (receiver_entry_table, connection, receiver_entry); +} + + +static gchar * +get_string_from_json_object (JsonObject * object) +{ + JsonNode *root; + JsonGenerator *generator; + gchar *text; + + /* Make it the root node */ + root = json_node_init_object (json_node_alloc (), object); + generator = json_generator_new (); + json_generator_set_root (generator, root); + text = json_generator_to_data (generator, NULL); + + /* Release everything */ + g_object_unref (generator); + json_node_free (root); + return text; +} + + +gboolean +exit_sighandler (gpointer user_data) +{ + g_print ("Caught signal, stopping mainloop\n"); + GMainLoop *mainloop = (GMainLoop *) user_data; + g_main_loop_quit (mainloop); + return TRUE; +} + + +int +main (int argc, char *argv[]) +{ + GMainLoop *mainloop; + SoupServer *soup_server; + GHashTable *receiver_entry_table; + + setlocale (LC_ALL, ""); + gst_init (&argc, &argv); + + receiver_entry_table = + g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + destroy_receiver_entry); + + mainloop = g_main_loop_new (NULL, FALSE); + g_assert (mainloop != NULL); + + g_unix_signal_add (SIGINT, exit_sighandler, mainloop); + g_unix_signal_add (SIGTERM, exit_sighandler, mainloop); + + soup_server = + soup_server_new (SOUP_SERVER_SERVER_HEADER, "webrtc-soup-server", NULL); + soup_server_add_handler (soup_server, "/", soup_http_handler, NULL, NULL); + soup_server_add_websocket_handler (soup_server, "/ws", NULL, NULL, + soup_websocket_handler, (gpointer) receiver_entry_table, NULL); + soup_server_listen_all (soup_server, SOUP_HTTP_PORT, + (SoupServerListenOptions) 0, NULL); + + g_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); + + g_main_loop_run (mainloop); + + g_object_unref (G_OBJECT (soup_server)); + g_hash_table_destroy (receiver_entry_table); + g_main_loop_unref (mainloop); + + gst_deinit (); + + return 0; +} From 219415dbf651002d68c217ae2cec06f808c5235b Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 8 May 2020 18:18:20 +1000 Subject: [PATCH 347/412] add vulkan example for android Part-of: --- vulkan/android/.gitignore | 10 + vulkan/android/AndroidManifest.xml | 16 + vulkan/android/build.gradle | 89 ++++ vulkan/android/gradle.properties | 23 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53637 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + vulkan/android/gradlew | 160 +++++++ vulkan/android/gradlew.bat | 90 ++++ vulkan/android/jni/Android.mk | 34 ++ vulkan/android/jni/Application.mk | 2 + vulkan/android/jni/dummy.cpp | 0 vulkan/android/jni/vulkan-1.c | 403 ++++++++++++++++++ vulkan/android/res/layout/main.xml | 45 ++ vulkan/android/res/values/strings.xml | 6 + .../vulkan/GStreamerSurfaceView.java | 85 ++++ .../freedesktop/gstreamer/vulkan/Vulkan1.java | 144 +++++++ 16 files changed, 1113 insertions(+) create mode 100644 vulkan/android/.gitignore create mode 100644 vulkan/android/AndroidManifest.xml create mode 100644 vulkan/android/build.gradle create mode 100644 vulkan/android/gradle.properties create mode 100644 vulkan/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 vulkan/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 vulkan/android/gradlew create mode 100644 vulkan/android/gradlew.bat create mode 100644 vulkan/android/jni/Android.mk create mode 100644 vulkan/android/jni/Application.mk create mode 100644 vulkan/android/jni/dummy.cpp create mode 100644 vulkan/android/jni/vulkan-1.c create mode 100644 vulkan/android/res/layout/main.xml create mode 100644 vulkan/android/res/values/strings.xml create mode 100644 vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java create mode 100644 vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java diff --git a/vulkan/android/.gitignore b/vulkan/android/.gitignore new file mode 100644 index 0000000000..553fb0d480 --- /dev/null +++ b/vulkan/android/.gitignore @@ -0,0 +1,10 @@ +.cxx/ +.externalNativeBuild/ +.gradle/ +.idea/ +assets/ +build/ +gst-build-*/ +local.properties +src/org/freedesktop/gstreamer/GStreamer.java +*.iml diff --git a/vulkan/android/AndroidManifest.xml b/vulkan/android/AndroidManifest.xml new file mode 100644 index 0000000000..1441174ada --- /dev/null +++ b/vulkan/android/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/vulkan/android/build.gradle b/vulkan/android/build.gradle new file mode 100644 index 0000000000..6fff9e990b --- /dev/null +++ b/vulkan/android/build.gradle @@ -0,0 +1,89 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion '28.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.vulkan.vulkan_1" + minSdkVersion 24 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + archivesBaseName = "$applicationId-v$versionCode" + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "vulkan-1" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} + +buildscript { + repositories { + jcenter() + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +repositories { + jcenter() + google() +} \ No newline at end of file diff --git a/vulkan/android/gradle.properties b/vulkan/android/gradle.properties new file mode 100644 index 0000000000..86aecfc4fb --- /dev/null +++ b/vulkan/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# gstAndroidRoot can be set to point to the unpacked GStreamer android top-level directory +# containing each architecture in subdirectories, or else set the GSTREAMER_ROOT_ANDROID +# environment variable to that location +# gstAndroidRoot=/path/to/gstreamer/android \ No newline at end of file diff --git a/vulkan/android/gradle/wrapper/gradle-wrapper.jar b/vulkan/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..05ef575b0cd0173fc735f2857ce4bd594ce4f6bd GIT binary patch literal 53637 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
IA| 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 t9m@SZ!E*7&jsr?!t7TL-WYI4eM@gAug8 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/vulkan/android/gradlew.bat b/vulkan/android/gradlew.bat new file mode 100644 index 0000000000..aec99730b4 --- /dev/null +++ b/vulkan/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/vulkan/android/jni/Android.mk b/vulkan/android/jni/Android.mk new file mode 100644 index 0000000000..c7c932b265 --- /dev/null +++ b/vulkan/android/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := vulkan-1 +LOCAL_SRC_FILES := vulkan-1.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog -landroid +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_EFFECTS) vulkan androidmedia +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 gobject-2.0 +GSTREAMER_EXTRA_LIBS := -liconv +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/vulkan/android/jni/Application.mk b/vulkan/android/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/vulkan/android/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/vulkan/android/jni/dummy.cpp b/vulkan/android/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vulkan/android/jni/vulkan-1.c b/vulkan/android/jni/vulkan-1.c new file mode 100644 index 0000000000..124e8cc4a8 --- /dev/null +++ b/vulkan/android/jni/vulkan-1.c @@ -0,0 +1,403 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +/* Structure to contain all our information, so we can pass it to callbacks */ +typedef struct _CustomData +{ + jobject app; /* Application instance, used to call its methods. A global reference is kept. */ + GstElement *pipeline; /* The running pipeline */ + GMainContext *context; /* GLib context used to run the main loop */ + GMainLoop *main_loop; /* GLib main loop */ + gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ + GstElement *video_sink; /* The video sink element which receives XOverlay commands */ + ANativeWindow *native_window; /* The Android native window where video will be rendered */ +} CustomData; + +/* These global variables cache values which are not changing during execution */ +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID custom_data_field_id; +static jmethodID set_message_method_id; +static jmethodID on_gstreamer_initialized_method_id; + +/* + * Private methods + */ + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* Change the content of the UI's TextView */ +static void +set_ui_message (const gchar * message, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GST_DEBUG ("Setting message to: %s", message); + jstring jmessage = (*env)->NewStringUTF (env, message); + (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + (*env)->DeleteLocalRef (env, jmessage); +} + +/* Retrieve errors from the bus and show them on the UI */ +static void +error_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GError *err; + gchar *debug_info; + gchar *message_string; + + gst_message_parse_error (msg, &err, &debug_info); + message_string = + g_strdup_printf ("Error received from element %s: %s", + GST_OBJECT_NAME (msg->src), err->message); + g_clear_error (&err); + g_free (debug_info); + set_ui_message (message_string, data); + g_free (message_string); + gst_element_set_state (data->pipeline, GST_STATE_NULL); +} + +/* Notify UI about pipeline state changes */ +static void +state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + /* Only pay attention to messages coming from the pipeline, not its children */ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { + gchar *message = g_strdup_printf ("State changed to %s", + gst_element_state_get_name (new_state)); + set_ui_message (message, data); + g_free (message); + } +} + +/* Check if all conditions are met to report GStreamer as initialized. + * These conditions will change depending on the application */ +static void +check_initialization_complete (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + if (!data->initialized && data->native_window && data->main_loop) { + GST_DEBUG + ("Initialization complete, notifying application. native_window:%p main_loop:%p", + data->native_window, data->main_loop); + + /* The main loop is running and we received a native window, inform the sink about it */ + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), + (guintptr) data->native_window); + + (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + data->initialized = TRUE; + } +} + +/* Main method for the native code. This is executed on its own thread. */ +static void * +app_function (void *userdata) +{ + JavaVMAttachArgs args; + GstBus *bus; + CustomData *data = (CustomData *) userdata; + GSource *bus_source; + GError *error = NULL; + + GST_DEBUG ("Creating pipeline in CustomData at %p", data); + + /* Create our own GLib Main Context and make it the default one */ + data->context = g_main_context_new (); + g_main_context_push_thread_default (data->context); + + /* Build pipeline */ + data->pipeline = + gst_parse_launch + ("videotestsrc ! warptv ! videoconvert ! vulkanupload ! vulkancolorconvert ! vulkansink", + &error); + if (error) { + gchar *message = + g_strdup_printf ("Unable to build pipeline: %s", error->message); + g_clear_error (&error); + set_ui_message (message, data); + g_free (message); + return NULL; + } + + /* Set the pipeline to READY, so it can already accept a window handle, if we have one */ + gst_element_set_state (data->pipeline, GST_STATE_READY); + + data->video_sink = + gst_bin_get_by_interface (GST_BIN (data->pipeline), + GST_TYPE_VIDEO_OVERLAY); + if (!data->video_sink) { + GST_ERROR ("Could not retrieve video sink"); + return NULL; + } + + /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ + bus = gst_element_get_bus (data->pipeline); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, data->context); + g_source_unref (bus_source); + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + data); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, data); + gst_object_unref (bus); + + /* Create a GLib Main Loop and set it to run */ + GST_DEBUG ("Entering main loop... (CustomData:%p)", data); + data->main_loop = g_main_loop_new (data->context, FALSE); + check_initialization_complete (data); + g_main_loop_run (data->main_loop); + GST_DEBUG ("Exited main loop"); + g_main_loop_unref (data->main_loop); + data->main_loop = NULL; + + /* Free resources */ + g_main_context_pop_thread_default (data->context); + g_main_context_unref (data->context); + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->video_sink); + gst_object_unref (data->pipeline); + + return NULL; +} + +/* + * Java Bindings + */ + +/* Instruct the native code to create its internal data structure, pipeline and thread */ +static void +gst_native_init (JNIEnv * env, jobject thiz) +{ + CustomData *data = g_new0 (CustomData, 1); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); + GST_DEBUG_CATEGORY_INIT (debug_category, "vulkan-1", 0, "Android vulkan 1"); + gst_debug_set_threshold_for_name ("vulkan-1", GST_LEVEL_DEBUG); + gst_debug_set_threshold_from_string ("3,*vulkan*:7", FALSE); + GST_DEBUG ("Created CustomData at %p", data); + data->app = (*env)->NewGlobalRef (env, thiz); + GST_DEBUG ("Created GlobalRef for app object at %p", data->app); + pthread_create (&gst_app_thread, NULL, &app_function, data); +} + +/* Quit the main loop, remove the native thread and free resources */ +static void +gst_native_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Quitting main loop..."); + g_main_loop_quit (data->main_loop); + GST_DEBUG ("Waiting for thread to finish..."); + pthread_join (gst_app_thread, NULL); + GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); + (*env)->DeleteGlobalRef (env, data->app); + GST_DEBUG ("Freeing CustomData at %p", data); + g_free (data); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); + GST_DEBUG ("Done finalizing"); +} + +/* Set pipeline to PLAYING state */ +static void +gst_native_play (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PLAYING"); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); +} + +/* Set pipeline to PAUSED state */ +static void +gst_native_pause (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PAUSED"); + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); +} + +/* Static class initializer: retrieve method and field IDs */ +static jboolean +gst_native_class_init (JNIEnv * env, jclass klass) +{ + custom_data_field_id = + (*env)->GetFieldID (env, klass, "native_custom_data", "J"); + set_message_method_id = + (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); + on_gstreamer_initialized_method_id = + (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); + + if (!custom_data_field_id || !set_message_method_id + || !on_gstreamer_initialized_method_id) { + /* We emit this message through the Android log instead of the GStreamer log because the later + * has not been initialized yet. + */ + __android_log_print (ANDROID_LOG_ERROR, "vulkan-1", + "The calling class does not implement all necessary interface methods"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +static void +gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface); + GST_DEBUG ("Received surface %p (native window %p)", surface, + new_native_window); + + if (data->native_window) { + ANativeWindow_release (data->native_window); + if (data->native_window == new_native_window) { + GST_DEBUG ("New native window is the same as the previous one %p", + data->native_window); + if (data->video_sink) { + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink)); + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink)); + } + return; + } else { + GST_DEBUG ("Released previous native window %p", data->native_window); + data->initialized = FALSE; + } + } + data->native_window = new_native_window; + + check_initialization_complete (data); +} + +static void +gst_native_surface_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Releasing Native Window %p", data->native_window); + + if (data->video_sink) { + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), + (guintptr) NULL); + gst_element_set_state (data->pipeline, GST_STATE_READY); + } + + ANativeWindow_release (data->native_window); + data->native_window = NULL; + data->initialized = FALSE; +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeInit", "()V", (void *) gst_native_init}, + {"nativeFinalize", "()V", (void *) gst_native_finalize}, + {"nativePlay", "()V", (void *) gst_native_play}, + {"nativePause", "()V", (void *) gst_native_pause}, + {"nativeSurfaceInit", "(Ljava/lang/Object;)V", + (void *) gst_native_surface_init}, + {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, + {"nativeClassInit", "()Z", (void *) gst_native_class_init} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "vulkan-1", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/vulkan/Vulkan1"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/vulkan/android/res/layout/main.xml b/vulkan/android/res/layout/main.xml new file mode 100644 index 0000000000..da2a993996 --- /dev/null +++ b/vulkan/android/res/layout/main.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/vulkan/android/res/values/strings.xml b/vulkan/android/res/values/strings.xml new file mode 100644 index 0000000000..9c1e03d484 --- /dev/null +++ b/vulkan/android/res/values/strings.xml @@ -0,0 +1,6 @@ + + + GStreamer Vulkan 1 + Play + Stop + diff --git a/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java new file mode 100644 index 0000000000..f040c675f6 --- /dev/null +++ b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java @@ -0,0 +1,85 @@ +package org.freedesktop.gstreamer.vulkan; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; + +// A simple SurfaceView whose width and height can be set from the outside +public class GStreamerSurfaceView extends SurfaceView { + public int media_width = 320; + public int media_height = 240; + + // Mandatory constructors, they do not do much + public GStreamerSurfaceView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public GStreamerSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GStreamerSurfaceView (Context context) { + super(context); + } + + // Called by the layout manager to find out our size and give us some rules. + // We will try to maximize our size, and preserve the media's aspect ratio if + // we are given the freedom to do so. + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0, height = 0; + int wmode = View.MeasureSpec.getMode(widthMeasureSpec); + int hmode = View.MeasureSpec.getMode(heightMeasureSpec); + int wsize = View.MeasureSpec.getSize(widthMeasureSpec); + int hsize = View.MeasureSpec.getSize(heightMeasureSpec); + + Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height); + // Obey width rules + switch (wmode) { + case View.MeasureSpec.AT_MOST: + if (hmode == View.MeasureSpec.EXACTLY) { + width = Math.min(hsize * media_width / media_height, wsize); + break; + } + case View.MeasureSpec.EXACTLY: + width = wsize; + break; + case View.MeasureSpec.UNSPECIFIED: + width = media_width; + } + + // Obey height rules + switch (hmode) { + case View.MeasureSpec.AT_MOST: + if (wmode == View.MeasureSpec.EXACTLY) { + height = Math.min(wsize * media_height / media_width, hsize); + break; + } + case View.MeasureSpec.EXACTLY: + height = hsize; + break; + case View.MeasureSpec.UNSPECIFIED: + height = media_height; + } + + // Finally, calculate best size when both axis are free + if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) { + int correct_height = width * media_height / media_width; + int correct_width = height * media_width / media_height; + + if (correct_height < height) + height = correct_height; + else + width = correct_width; + } + + // Obey minimum size + width = Math.max (getSuggestedMinimumWidth(), width); + height = Math.max (getSuggestedMinimumHeight(), height); + setMeasuredDimension(width, height); + } + +} diff --git a/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java new file mode 100644 index 0000000000..3d5ca153ef --- /dev/null +++ b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java @@ -0,0 +1,144 @@ +package org.freedesktop.gstreamer.vulkan; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.GStreamer; +import org.freedesktop.gstreamer.vulkan.vulkan_1.R; + +public class Vulkan1 extends Activity implements SurfaceHolder.Callback { + private native void nativeInit(); // Initialize native code, build pipeline, etc + private native void nativeFinalize(); // Destroy pipeline and shutdown native code + private native void nativePlay(); // Set pipeline to PLAYING + private native void nativePause(); // Set pipeline to PAUSED + private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks + private native void nativeSurfaceInit(Object surface); + private native void nativeSurfaceFinalize(); + private long native_custom_data; // Native code will use this to keep private data + + private boolean is_playing_desired; // Whether the user asked to go to PLAYING + + // Called when the activity is first created. + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Initialize GStreamer and warn if it fails + try { + GStreamer.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + ImageButton play = (ImageButton) this.findViewById(R.id.button_play); + play.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = true; + nativePlay(); + } + }); + + ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop); + pause.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = false; + nativePause(); + } + }); + + SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); + SurfaceHolder sh = sv.getHolder(); + sh.addCallback(this); + + if (savedInstanceState != null) { + is_playing_desired = savedInstanceState.getBoolean("playing"); + Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired); + } else { + is_playing_desired = false; + Log.i ("GStreamer", "Activity created. There is no saved state, playing: false"); + } + + // Start with disabled buttons, until native code is initialized + this.findViewById(R.id.button_play).setEnabled(false); + this.findViewById(R.id.button_stop).setEnabled(false); + + nativeInit(); + } + + protected void onSaveInstanceState (Bundle outState) { + Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired); + outState.putBoolean("playing", is_playing_desired); + } + + protected void onDestroy() { + nativeFinalize(); + super.onDestroy(); + } + + // Called from native code. This sets the content of the TextView from the UI thread. + private void setMessage(final String message) { + final TextView tv = (TextView) this.findViewById(R.id.textview_message); + runOnUiThread (new Runnable() { + public void run() { + tv.setText(message); + } + }); + } + + // Called from native code. Native code calls this once it has created its pipeline and + // the main loop is running, so it is ready to accept commands. + private void onGStreamerInitialized () { + Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired); + // Restore previous playing state + if (is_playing_desired) { + nativePlay(); + } else { + nativePause(); + } + + // Re-enable buttons, now that GStreamer is initialized + final Activity activity = this; + runOnUiThread(new Runnable() { + public void run() { + activity.findViewById(R.id.button_play).setEnabled(true); + activity.findViewById(R.id.button_stop).setEnabled(true); + } + }); + } + + static { + System.loadLibrary("gstreamer_android"); + System.loadLibrary("vulkan-1"); + nativeClassInit(); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + + width + " height " + height); + nativeSurfaceInit (holder.getSurface()); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.d("GStreamer", "Surface created: " + holder.getSurface()); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d("GStreamer", "Surface destroyed"); + nativeSurfaceFinalize (); + } + +} From c0f303eacfa172277fbaad242d4064416b8322a2 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Thu, 14 May 2020 11:04:37 +0100 Subject: [PATCH 348/412] janus: Remove unused parameters and refactor --- webrtc/janus/janusvideoroom.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/webrtc/janus/janusvideoroom.py b/webrtc/janus/janusvideoroom.py index 4372a6361f..37a6824434 100644 --- a/webrtc/janus/janusvideoroom.py +++ b/webrtc/janus/janusvideoroom.py @@ -243,14 +243,12 @@ class JanusGateway: return raw class WebRTCClient: - def __init__(self, id_, peer_id, server, signaling): - self.id_ = id_ + def __init__(self, peer_id, server): self.conn = None self.pipe = None self.webrtc = None self.peer_id = peer_id - self.server = server or 'wss://127.0.0.1:8989' - self.signaling = signaling + self.signaling = JanusGateway(server) self.request = None self.offermsg = None @@ -378,7 +376,8 @@ class WebRTCClient: sdpmlineindex = ice['sdpMLineIndex'] self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) - async def loop(self, signaling): + async def loop(self): + signaling = self.signaling await signaling.connect() await signaling.attach("janus.plugin.videoroom") @@ -420,6 +419,8 @@ class WebRTCClient: return 0 + async def close(self): + return await self.signaling.close() def check_plugins(): needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", @@ -439,16 +440,14 @@ if __name__=='__main__': parser.add_argument('label', help='videoroom label') parser.add_argument('--server', help='Signalling server to connect to, eg "wss://127.0.0.1:8989"') args = parser.parse_args() - our_id = random.randrange(10, 10000) - signaling = JanusGateway(args.server) - c = WebRTCClient(our_id, args.label, args.server, signaling) + c = WebRTCClient(args.label, args.server) loop = asyncio.get_event_loop() try: loop.run_until_complete( - c.loop(signaling) + c.loop() ) except KeyboardInterrupt: pass finally: print("Interrupted, cleaning up") - loop.run_until_complete(signaling.close()) + loop.run_until_complete(c.close()) From 180e1ce24c7742b983f0f662ab2d0421988272e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 22 May 2020 22:45:35 +0300 Subject: [PATCH 349/412] Update dependencies of Rust demos --- .../multiparty-sendrecv/gst-rust/Cargo.lock | 712 +++++++++--------- .../multiparty-sendrecv/gst-rust/Cargo.toml | 2 +- webrtc/sendrecv/gst-rust/Cargo.lock | 394 +++++----- webrtc/sendrecv/gst-rust/Cargo.toml | 2 +- 4 files changed, 523 insertions(+), 587 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock index 7fce37ce1c..4d0e83c5f9 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock @@ -2,70 +2,67 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.26" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "async-native-tls" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-std" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-task 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-task" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-tungstenite" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "autocfg" version = "1.0.0" @@ -88,7 +85,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -107,7 +104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -115,17 +112,9 @@ name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cc" -version = "1.0.50" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -135,7 +124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -145,54 +134,57 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-foundation-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "crossbeam-channel" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-deque" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-epoch" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-utils" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -212,7 +204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -244,67 +236,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "futures-channel" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "futures-core" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures-executor" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "futures-io" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures-macro" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "futures-sink" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures-task" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "futures-timer" @@ -313,19 +308,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures-util" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -334,7 +330,7 @@ name = "generic-array" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -343,25 +339,25 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -369,7 +365,7 @@ name = "glib-sys" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -379,40 +375,40 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-sdp" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -425,7 +421,7 @@ dependencies = [ "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -436,23 +432,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-webrtc" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -464,7 +460,7 @@ dependencies = [ "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -478,19 +474,19 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.6" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "http" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -522,7 +518,7 @@ name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -541,7 +537,7 @@ dependencies = [ [[package]] name = "kv-log-macro" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -554,7 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -570,22 +566,27 @@ name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memchr" -version = "2.3.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -593,22 +594,22 @@ dependencies = [ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mio-uds" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -617,7 +618,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -629,28 +630,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "native-tls" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.27 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "net2" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -665,7 +666,7 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -683,16 +684,16 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "once_cell" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -702,15 +703,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.10.27" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -720,34 +721,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "paste" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", + "paste-impl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "paste-impl" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -757,30 +758,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pin-project" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-internal" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pin-utils" -version = "0.1.0-alpha.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -790,51 +791,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-error" -version = "0.4.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "proc-macro-nested" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "1.0.8" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -842,10 +838,10 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -854,18 +850,18 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -898,32 +894,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustversion" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "schannel" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -932,64 +910,53 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "security-framework" -version = "0.3.4" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "security-framework-sys" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" -version = "1.0.104" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.45" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1010,38 +977,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "1.0.14" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1050,9 +1017,9 @@ name = "syn-mid" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1061,7 +1028,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1078,31 +1045,31 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.10" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thiserror-impl" -version = "1.0.10" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tungstenite" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1114,7 +1081,7 @@ dependencies = [ [[package]] name = "typenum" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1130,7 +1097,7 @@ name = "unicode-normalization" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1168,6 +1135,11 @@ name = "vcpkg" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1177,18 +1149,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "webrtc-app" version = "0.1.0" dependencies = [ - "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-tungstenite 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-webrtc 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-tungstenite 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-webrtc 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1230,135 +1202,130 @@ dependencies = [ ] [metadata] -"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" -"checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" -"checksum async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e" -"checksum async-task 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f20c6fda19d0fc02406779587ca4f9a4171cd32e4a5bda0bd016f0a1334c8d4a" -"checksum async-tungstenite 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f1a7fca4f1ccd1fe926bec21a4ba6259d3d91f04d67a9dd76bd549366be5bef" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +"checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" +"checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +"checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +"checksum async-tungstenite 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "182617e5bbfe7001b6f2317883506f239c77313171620a04cc11292704d3e171" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +"checksum cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" -"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" -"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" -"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" -"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" -"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" -"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" -"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" -"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" -"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" -"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" +"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" +"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" "checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" -"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum glib 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27bafffe3fc615449d5a87705f93f6fe4fcf749662b9d08cc9d5451f6c1b0f21" +"checksum glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" "checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" "checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" -"checksum gstreamer 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f439859b5f3310518adc0aea572ac89ddd94f8ba3af4626d6156733671599629" -"checksum gstreamer-sdp 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "674df58b85cb077a357c581c29796fbeb5aa36e8362269807a11f938e5c7b973" +"checksum gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5918f609a4309c98347fc58b7259eac99fe5c69adcdd23cf617d967d7d4da1d1" +"checksum gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9f2fcf072e733c9ecd29ce40df432148304995827b481a16e3a2adf57a6dfa" "checksum gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" "checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" -"checksum gstreamer-webrtc 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64235af90676896b01f3cbe50808fefaf498057ca288ce4a31a89de81464e04" +"checksum gstreamer-webrtc 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" "checksum gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" -"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +"checksum kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" -"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" -"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +"checksum mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" -"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" -"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.27 (registry+https://github.com/rust-lang/crates.io-index)" = "e176a45fedd4c990e26580847a525e39e16ec32ac78957dbf62ded31b3abfd6f" +"checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" -"checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" -"checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" +"checksum openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)" = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" +"checksum paste 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" +"checksum paste-impl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" -"checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" -"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro-error 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" -"checksum proc-macro-error-attr 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" -"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +"checksum proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" +"checksum proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" +"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +"checksum proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" +"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" -"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" -"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" -"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" +"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +"checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +"checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +"checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" -"checksum structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" -"checksum structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" -"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +"checksum structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" +"checksum structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" +"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" -"checksum thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" -"checksum tungstenite 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08f33f14c64af9dfc6e50e6812261178d6bd434c0e501f87c7683b8aaa9eb8d3" -"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" +"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" +"checksum tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" @@ -1367,6 +1334,7 @@ dependencies = [ "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml index 94e6533f6c..ed9812ed71 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml @@ -10,7 +10,7 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.4", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.5", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } diff --git a/webrtc/sendrecv/gst-rust/Cargo.lock b/webrtc/sendrecv/gst-rust/Cargo.lock index cebc8e6043..cf2cbd7396 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.lock +++ b/webrtc/sendrecv/gst-rust/Cargo.lock @@ -2,26 +2,27 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.26" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" [[package]] name = "async-native-tls" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" +checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" dependencies = [ "async-std", "native-tls", "thiserror", + "url", ] [[package]] name = "async-std" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" dependencies = [ "async-task", "crossbeam-channel", @@ -44,9 +45,9 @@ dependencies = [ [[package]] name = "async-task" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f20c6fda19d0fc02406779587ca4f9a4171cd32e4a5bda0bd016f0a1334c8d4a" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" dependencies = [ "libc", "winapi 0.3.8", @@ -54,24 +55,19 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1a7fca4f1ccd1fe926bec21a4ba6259d3d91f04d67a9dd76bd549366be5bef" +checksum = "182617e5bbfe7001b6f2317883506f239c77313171620a04cc11292704d3e171" dependencies = [ "async-native-tls", "async-std", - "futures", + "futures-io", + "futures-util", "log", "pin-project", "tungstenite", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.0" @@ -119,9 +115,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" @@ -129,20 +125,11 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - [[package]] name = "cc" -version = "1.0.50" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -152,9 +139,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "bitflags", "textwrap", @@ -163,9 +150,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ "core-foundation-sys", "libc", @@ -173,50 +160,53 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "crossbeam-channel" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" dependencies = [ "crossbeam-utils", + "maybe-uninit", ] [[package]] name = "crossbeam-deque" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ "crossbeam-epoch", "crossbeam-utils", + "maybe-uninit", ] [[package]] name = "crossbeam-epoch" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 0.1.7", + "autocfg", "cfg-if", "crossbeam-utils", "lazy_static", + "maybe-uninit", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 0.1.7", + "autocfg", "cfg-if", "lazy_static", ] @@ -238,9 +228,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -275,9 +265,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" dependencies = [ "futures-channel", "futures-core", @@ -290,9 +280,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" dependencies = [ "futures-core", "futures-sink", @@ -300,15 +290,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" [[package]] name = "futures-executor" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" dependencies = [ "futures-core", "futures-task", @@ -317,15 +307,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-macro" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -335,15 +325,18 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" [[package]] name = "futures-task" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] [[package]] name = "futures-timer" @@ -353,9 +346,9 @@ checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" [[package]] name = "futures-util" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ "futures-channel", "futures-core", @@ -364,6 +357,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", + "pin-project", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -392,9 +386,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bafffe3fc615449d5a87705f93f6fe4fcf749662b9d08cc9d5451f6c1b0f21" +checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" dependencies = [ "bitflags", "futures-channel", @@ -431,9 +425,9 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f439859b5f3310518adc0aea572ac89ddd94f8ba3af4626d6156733671599629" +checksum = "5918f609a4309c98347fc58b7259eac99fe5c69adcdd23cf617d967d7d4da1d1" dependencies = [ "bitflags", "cfg-if", @@ -453,9 +447,9 @@ dependencies = [ [[package]] name = "gstreamer-sdp" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "674df58b85cb077a357c581c29796fbeb5aa36e8362269807a11f938e5c7b973" +checksum = "1b9f2fcf072e733c9ecd29ce40df432148304995827b481a16e3a2adf57a6dfa" dependencies = [ "glib", "glib-sys", @@ -492,9 +486,9 @@ dependencies = [ [[package]] name = "gstreamer-webrtc" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64235af90676896b01f3cbe50808fefaf498057ca288ce4a31a89de81464e04" +checksum = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" dependencies = [ "glib", "glib-sys", @@ -531,18 +525,18 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.6" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] [[package]] name = "http" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ "bytes", "fnv", @@ -602,9 +596,9 @@ dependencies = [ [[package]] name = "kv-log-macro" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" dependencies = [ "log", ] @@ -617,9 +611,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "log" @@ -637,25 +631,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] -name = "memchr" -version = "2.3.0" +name = "maybe-uninit" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ - "rustc_version", + "autocfg", ] [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -672,9 +672,9 @@ dependencies = [ [[package]] name = "mio-uds" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", @@ -701,9 +701,9 @@ checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" [[package]] name = "native-tls" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" +checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" dependencies = [ "lazy_static", "libc", @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", @@ -734,17 +734,17 @@ version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ - "autocfg 1.0.0", + "autocfg", "num-traits", ] [[package]] name = "num-rational" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.0.0", + "autocfg", "num-integer", "num-traits", ] @@ -755,14 +755,14 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ - "autocfg 1.0.0", + "autocfg", ] [[package]] name = "num_cpus" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "opaque-debug" @@ -782,9 +782,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" -version = "0.10.27" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e176a45fedd4c990e26580847a525e39e16ec32ac78957dbf62ded31b3abfd6f" +checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" dependencies = [ "bitflags", "cfg-if", @@ -802,11 +802,11 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" +checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" dependencies = [ - "autocfg 1.0.0", + "autocfg", "cc", "libc", "pkg-config", @@ -815,9 +815,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" +checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" dependencies = [ "paste-impl", "proc-macro-hack", @@ -825,9 +825,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" +checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -843,18 +843,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ "proc-macro2", "quote", @@ -863,15 +863,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" [[package]] name = "pin-utils" -version = "0.1.0-alpha.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" @@ -881,67 +881,62 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "proc-macro-error" -version = "0.4.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" +checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "rustversion", "syn", + "version_check", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" +checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" dependencies = [ "proc-macro2", "quote", - "rustversion", "syn", "syn-mid", + "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro-nested" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.8" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +checksum = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ "proc-macro2", ] @@ -961,11 +956,11 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "c2-chacha", + "ppv-lite86", "rand_core", ] @@ -1002,37 +997,17 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" [[package]] name = "schannel" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", "winapi 0.3.8", @@ -1040,16 +1015,17 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" -version = "0.3.4" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" dependencies = [ + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1058,39 +1034,25 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" dependencies = [ "core-foundation-sys", + "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" -version = "1.0.104" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote", @@ -1099,9 +1061,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.45" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", @@ -1128,15 +1090,15 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "structopt" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" +checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" dependencies = [ "clap", "lazy_static", @@ -1145,9 +1107,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" +checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" dependencies = [ "heck", "proc-macro-error", @@ -1158,9 +1120,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.14" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" dependencies = [ "proc-macro2", "quote", @@ -1203,18 +1165,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.10" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" +checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.10" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" +checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", "quote", @@ -1223,9 +1185,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f33f14c64af9dfc6e50e6812261178d6bd434c0e501f87c7683b8aaa9eb8d3" +checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" dependencies = [ "base64", "byteorder", @@ -1242,9 +1204,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-bidi" @@ -1305,6 +1267,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/webrtc/sendrecv/gst-rust/Cargo.toml b/webrtc/sendrecv/gst-rust/Cargo.toml index 94e6533f6c..ed9812ed71 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.toml +++ b/webrtc/sendrecv/gst-rust/Cargo.toml @@ -10,7 +10,7 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.4", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.5", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } From 78df1ca74ccee09a83b9e30eb307b7f93056e469 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:28:29 +0000 Subject: [PATCH 350/412] simple_server: Correctly pass health option It was completely ignored. Also don't de-serialize options. Just parse them directly in `__init__`. Less error-prone. --- webrtc/signalling/simple_server.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index 7da614bb05..7f9123e116 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -17,9 +17,10 @@ import argparse import http import concurrent + class WebRTCSimpleServer(object): - def __init__(self, addr, port, keepalive_timeout, disable_ssl, certpath, health_path=None): + def __init__(self, options): ############### Global data ############### # Format: {uid: (Peer WebSocketServerProtocol, @@ -34,17 +35,18 @@ class WebRTCSimpleServer(object): # Room dict with a set of peers in each room self.rooms = dict() - self.keepalive_timeout = keepalive_timeout - self.addr = addr - self.port = port - self.disable_ssl = disable_ssl - self.certpath = certpath - self.health_path = health_path + # Options + self.addr = options.addr + self.port = options.port + self.keepalive_timeout = options.keepalive_timeout + self.cert_path = options.cert_path + self.disable_ssl = options.disable_ssl + self.health_path = options.health ############### Helper functions ############### async def health_check(self, path, request_headers): - if path == self.health_part: + if path == self.health_path: return http.HTTPStatus.OK, [], b"OK\n" return None @@ -280,7 +282,7 @@ def main(): loop = asyncio.get_event_loop() - r = WebRTCSimpleServer(options.addr, options.port, options.keepalive_timeout, options.disable_ssl, options.cert_path) + r = WebRTCSimpleServer(options) loop.run_until_complete (r.run()) loop.run_forever () From b8c1bd1fa344acb6f6ca591e0687cd0cba9330a3 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:29:53 +0000 Subject: [PATCH 351/412] simple_server: Fix init of websockets log handler This has changed since the original code was written: https://websockets.readthedocs.io/en/stable/cheatsheet.html#debugging --- webrtc/signalling/simple_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index 7f9123e116..9cf2e36ece 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -261,9 +261,9 @@ class WebRTCSimpleServer(object): # https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol max_queue=16) - logger = logging.getLogger('websockets.server') - - logger.setLevel(logging.ERROR) + # Setup logging + logger = logging.getLogger('websockets') + logger.setLevel(logging.INFO) logger.addHandler(logging.StreamHandler()) return wsd From 7b96b06752c349ebb3b1e93a6476b46e698adfac Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:32:43 +0000 Subject: [PATCH 352/412] simple_server: Make the server class loop-aware First step in making the class able to manage its own state. --- webrtc/signalling/simple_server.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index 9cf2e36ece..2e43786ed6 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -20,7 +20,7 @@ import concurrent class WebRTCSimpleServer(object): - def __init__(self, options): + def __init__(self, loop, options): ############### Global data ############### # Format: {uid: (Peer WebSocketServerProtocol, @@ -35,6 +35,11 @@ class WebRTCSimpleServer(object): # Room dict with a set of peers in each room self.rooms = dict() + # Event loop + self.loop = loop + # Websocket Server Instance + self.server = None + # Options self.addr = options.addr self.port = options.port @@ -266,7 +271,8 @@ class WebRTCSimpleServer(object): logger.setLevel(logging.INFO) logger.addHandler(logging.StreamHandler()) - return wsd + # Run the server + self.server = self.loop.run_until_complete(wsd) def main(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -282,11 +288,12 @@ def main(): loop = asyncio.get_event_loop() - r = WebRTCSimpleServer(options) + r = WebRTCSimpleServer(loop, options) - loop.run_until_complete (r.run()) - loop.run_forever () - print ("Goodbye!") + print('Starting server...') + r.run() + loop.run_forever() + print("Goodbye!") if __name__ == "__main__": main() From 4761396d870b329353ffb3f33d9f1bfc78c465ea Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:33:32 +0000 Subject: [PATCH 353/412] simple_server: Abstract out ssl context generation --- webrtc/signalling/simple_server.py | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index 2e43786ed6..f7ee545505 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -222,28 +222,33 @@ class WebRTCSimpleServer(object): await ws.send('HELLO') return uid + def get_ssl_certs(self): + if 'letsencrypt' in self.cert_path: + chain_pem = os.path.join(self.cert_path, 'fullchain.pem') + key_pem = os.path.join(self.cert_path, 'privkey.pem') + else: + chain_pem = os.path.join(self.cert_path, 'cert.pem') + key_pem = os.path.join(self.cert_path, 'key.pem') + return chain_pem, key_pem + + def get_ssl_ctx(self): + if self.disable_ssl: + return None + # Create an SSL context to be used by the websocket server + print('Using TLS with keys in {!r}'.format(self.cert_path)) + chain_pem, key_pem = self.get_ssl_certs() + sslctx = ssl.create_default_context() + try: + sslctx.load_cert_chain(chain_pem, keyfile=key_pem) + except FileNotFoundError: + print("Certificates not found, did you run generate_cert.sh?") + sys.exit(1) + # FIXME + sslctx.check_hostname = False + sslctx.verify_mode = ssl.CERT_NONE + return sslctx + def run(self): - sslctx = None - if not self.disable_ssl: - # Create an SSL context to be used by the websocket server - print('Using TLS with keys in {!r}'.format(self.certpath)) - if 'letsencrypt' in self.certpath: - chain_pem = os.path.join(self.certpath, 'fullchain.pem') - key_pem = os.path.join(self.certpath, 'privkey.pem') - else: - chain_pem = os.path.join(self.certpath, 'cert.pem') - key_pem = os.path.join(self.certpath, 'key.pem') - - sslctx = ssl.create_default_context() - try: - sslctx.load_cert_chain(chain_pem, keyfile=key_pem) - except FileNotFoundError: - print("Certificates not found, did you run generate_cert.sh?") - sys.exit(1) - # FIXME - sslctx.check_hostname = False - sslctx.verify_mode = ssl.CERT_NONE - async def handler(ws, path): ''' All incoming messages are handled here. @path is unused. @@ -258,6 +263,8 @@ class WebRTCSimpleServer(object): finally: await self.remove_peer(peer_id) + sslctx = self.get_ssl_ctx() + print("Listening on https://{}:{}".format(self.addr, self.port)) # Websocket server wsd = websockets.serve(handler, self.addr, self.port, ssl=sslctx, process_request=self.health_check if self.health_path else None, From 77ae10ab663bda858c03a5becd452a269c70ca19 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:34:11 +0000 Subject: [PATCH 354/412] simple_server: Restart when the certificate changes Reload the SSL context and restart the server if the certificate changes. Without this, new connections will continue to use the old expired certificate. --- webrtc/signalling/simple_server.py | 43 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index f7ee545505..12153a0f85 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -44,10 +44,14 @@ class WebRTCSimpleServer(object): self.addr = options.addr self.port = options.port self.keepalive_timeout = options.keepalive_timeout + self.cert_restart = options.cert_restart self.cert_path = options.cert_path self.disable_ssl = options.disable_ssl self.health_path = options.health + # Certificate mtime, used to detect when to restart the server + self.cert_mtime = -1 + ############### Helper functions ############### async def health_check(self, path, request_headers): @@ -280,6 +284,38 @@ class WebRTCSimpleServer(object): # Run the server self.server = self.loop.run_until_complete(wsd) + # Stop the server if certificate changes + self.loop.run_until_complete(self.check_server_needs_restart()) + + async def stop(self): + print('Stopping server... ', end='') + self.server.close() + await self.server.wait_closed() + self.loop.stop() + print('Stopped.') + + def check_cert_changed(self): + chain_pem, key_pem = self.get_ssl_certs() + mtime = max(os.stat(key_pem).st_mtime, os.stat(chain_pem).st_mtime) + if self.cert_mtime < 0: + self.cert_mtime = mtime + return False + if mtime > self.cert_mtime: + self.cert_mtime = mtime + return True + return False + + async def check_server_needs_restart(self): + "When the certificate changes, we need to restart the server" + if not self.cert_restart: + return + while True: + await asyncio.sleep(10) + if self.check_cert_changed(): + print('Certificate changed, stopping server...') + await self.stop() + return + def main(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -290,6 +326,7 @@ def main(): parser.add_argument('--cert-path', default=os.path.dirname(__file__)) parser.add_argument('--disable-ssl', default=False, help='Disable ssl', action='store_true') parser.add_argument('--health', default='/health', help='Health check route') + parser.add_argument('--restart-on-cert-change', default=False, dest='cert_restart', action='store_true', help='Automatically restart if the SSL certificate changes') options = parser.parse_args(sys.argv[1:]) @@ -298,8 +335,10 @@ def main(): r = WebRTCSimpleServer(loop, options) print('Starting server...') - r.run() - loop.run_forever() + while True: + r.run() + loop.run_forever() + print('Restarting server...') print("Goodbye!") if __name__ == "__main__": From 0776def18cf80317bb6864d69b3cfdc88bd723c8 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 25 May 2020 18:39:16 +0000 Subject: [PATCH 355/412] simple_server: asyncio TimeoutError has moved We didn't notice this because the logging was broken. --- webrtc/signalling/simple_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/signalling/simple_server.py b/webrtc/signalling/simple_server.py index 12153a0f85..0cae633211 100755 --- a/webrtc/signalling/simple_server.py +++ b/webrtc/signalling/simple_server.py @@ -68,7 +68,7 @@ class WebRTCSimpleServer(object): while msg is None: try: msg = await asyncio.wait_for(ws.recv(), self.keepalive_timeout) - except (asyncio.exceptions.TimeoutError, concurrent.futures._base.TimeoutError): + except (asyncio.TimeoutError, concurrent.futures._base.TimeoutError): print('Sending keepalive ping to {!r} in recv'.format(raddr)) await ws.ping() return msg From 17f84bfd81ada54d77509f0fe24a4587331308c4 Mon Sep 17 00:00:00 2001 From: Corey Cole Date: Fri, 5 Jun 2020 16:19:12 -0700 Subject: [PATCH 356/412] fix: python webrtc_sendrecv.py typo --- webrtc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/README.md b/webrtc/README.md index 09000ba0f3..59bec73646 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -78,7 +78,7 @@ $ gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstrea #### Running the Python version * python3 -m pip install --user websockets -* run `python3 sendrecv/gst/webrtc-sendrecv.py ID` with the `id` from the browser. You will see state changes and an SDP exchange. +* run `python3 sendrecv/gst/webrtc_sendrecv.py ID` with the `id` from the browser. You will see state changes and an SDP exchange. > The python version requires at least version 1.14.2 of gstreamer and its plugins. From 751d06af6fc2c5a3570138ebb7c4f0b56a0af7ab Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 16 Jun 2020 12:50:21 +0530 Subject: [PATCH 357/412] signalling: Fix simple-server script name in Dockerfile Fixes https://github.com/centricular/gstwebrtc-demos/issues/202 --- webrtc/signalling/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/signalling/Dockerfile b/webrtc/signalling/Dockerfile index 5902f68ae8..4d0b655890 100644 --- a/webrtc/signalling/Dockerfile +++ b/webrtc/signalling/Dockerfile @@ -5,4 +5,4 @@ RUN pip3 install --user websockets WORKDIR /opt/ COPY . /opt/ -CMD python -u ./simple-server.py --disable-ssl +CMD python -u ./simple_server.py --disable-ssl From 5f8bf174e8a9cd1b69c03be24941305078fbeeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 19 Jun 2020 19:28:16 +0100 Subject: [PATCH 358/412] Release 1.17.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ea0721e7cf..13bdb1a9aa 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.0.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.17.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 01882c92d1769581d94ee8a989494f86f8da4cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 20 Jun 2020 00:28:41 +0100 Subject: [PATCH 359/412] Back to development --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 13bdb1a9aa..9ac22b02ba 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.17.1.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From d44b2316fa2128e082dbe4b583919cceb8c3197f Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 22 Jun 2020 17:39:12 +0530 Subject: [PATCH 360/412] sendonly: Don't assume we're building on UNIX Fixes https://github.com/centricular/gstwebrtc-demos/issues/203 --- webrtc/sendonly/webrtc-recvonly-h264.c | 13 ++++++++----- webrtc/sendonly/webrtc-unidirectional-h264.c | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/webrtc/sendonly/webrtc-recvonly-h264.c b/webrtc/sendonly/webrtc-recvonly-h264.c index 9ba7fe4926..87b24f2f26 100644 --- a/webrtc/sendonly/webrtc-recvonly-h264.c +++ b/webrtc/sendonly/webrtc-recvonly-h264.c @@ -1,9 +1,12 @@ #include #include -#include #include #include +#ifdef G_OS_UNIX +#include +#endif + #define GST_USE_UNSTABLE_API #include @@ -50,8 +53,6 @@ void soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, static gchar *get_string_from_json_object (JsonObject * object); -gboolean exit_sighandler (gpointer user_data); - @@ -649,7 +650,7 @@ get_string_from_json_object (JsonObject * object) return text; } - +#ifdef G_OS_UNIX gboolean exit_sighandler (gpointer user_data) { @@ -658,7 +659,7 @@ exit_sighandler (gpointer user_data) g_main_loop_quit (mainloop); return TRUE; } - +#endif int main (int argc, char *argv[]) @@ -677,8 +678,10 @@ main (int argc, char *argv[]) mainloop = g_main_loop_new (NULL, FALSE); g_assert (mainloop != NULL); +#ifdef G_OS_UNIX g_unix_signal_add (SIGINT, exit_sighandler, mainloop); g_unix_signal_add (SIGTERM, exit_sighandler, mainloop); +#endif soup_server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "webrtc-soup-server", NULL); diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index 4887c7eed2..b8060145fe 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -1,9 +1,12 @@ #include #include -#include #include #include +#ifdef G_OS_UNIX +#include +#endif + #define GST_USE_UNSTABLE_API #include @@ -42,8 +45,6 @@ void soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, static gchar *get_string_from_json_object (JsonObject * object); -gboolean exit_sighandler (gpointer user_data); - struct _ReceiverEntry { SoupWebsocketConnection *connection; @@ -531,7 +532,7 @@ get_string_from_json_object (JsonObject * object) return text; } - +#ifdef G_OS_UNIX gboolean exit_sighandler (gpointer user_data) { @@ -540,7 +541,7 @@ exit_sighandler (gpointer user_data) g_main_loop_quit (mainloop); return TRUE; } - +#endif int main (int argc, char *argv[]) @@ -559,8 +560,10 @@ main (int argc, char *argv[]) mainloop = g_main_loop_new (NULL, FALSE); g_assert (mainloop != NULL); +#ifdef G_OS_UNIX g_unix_signal_add (SIGINT, exit_sighandler, mainloop); g_unix_signal_add (SIGTERM, exit_sighandler, mainloop); +#endif soup_server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "webrtc-soup-server", NULL); From e1c3dad25812f94669584bc0d97e4f78ad134ed8 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 19 Jun 2020 01:31:02 +1000 Subject: [PATCH 361/412] webrtc: update for move to gst-examples - Integrate with the build system. - Some README updates. Part-of: --- meson.build | 11 +++++ webrtc/README.md | 68 ++++++++++++++---------------- webrtc/check/meson.build | 23 +++++----- webrtc/meson.build | 19 --------- webrtc/signalling/generate_cert.sh | 8 +++- webrtc/signalling/meson.build | 14 +++--- 6 files changed, 72 insertions(+), 71 deletions(-) diff --git a/meson.build b/meson.build index 9ac22b02ba..7178b2a5aa 100644 --- a/meson.build +++ b/meson.build @@ -3,6 +3,12 @@ project('gst-examples', 'c', version : '1.17.1.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) +if cc.get_id() == 'msvc' + add_project_arguments( + cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8 + language : 'c') +endif + glib_dep = dependency('glib-2.0', version: '>= 2.38', fallback: ['glib', 'libglib_dep']) gio_dep = dependency('gio-2.0', @@ -18,6 +24,11 @@ gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.7.1.1', fallback: ['gst-plugins-bad', 'gstplayer_dep']) gsttag_dep = dependency('gstreamer-tag-1.0', fallback: ['gst-plugins-base', 'tag_dep']) +gstwebrtc_dep = dependency('gstreamer-webrtc-1.0', version: '>= 1.14.0', + fallback: ['gst-plugins-bad', 'gstwebrtc_dep']) +gstsdp_dep = dependency('gstreamer-webrtc-1.0', + fallback : ['gst-plugins-bad', 'gstwebrtc_dep']) subdir('playback') subdir('network') +subdir('webrtc') diff --git a/webrtc/README.md b/webrtc/README.md index 59bec73646..d9e728f2a7 100644 --- a/webrtc/README.md +++ b/webrtc/README.md @@ -14,39 +14,35 @@ If you don't want to use the binaries provided by GStreamer or on your Linux dis The easiest way to build the webrtc plugin and all the plugins it needs, is to [use Cerbero](https://gstreamer.freedesktop.org/documentation/installing/building-from-source-using-cerbero.html). These instructions should work out of the box for all platforms, including cross-compiling for iOS and Android. -One thing to note is that it's written in Python 2, so you may need to replace all instances of `./cerbero-uninstalled` (or `cerbero`) with `python2 cerbero-uninstalled` or whatever Python 2 is called on your platform. - ## Building GStreamer manually from source +For hacking on the webrtc plugin, you may want to build manually using the git repositories: + + - http://gitlab.freedesktop.org/gstreamer/gstreamer + - http://gitlab.freedesktop.org/gstreamer/gst-plugins-base + - http://gitlab.freedesktop.org/gstreamer/gst-plugins-good + - http://gitlab.freedesktop.org/gstreamer/gst-plugins-bad + - http://gitlab.freedesktop.org/libnice/libnice + +Or with Meson gst-build: + +https://gitlab.freedesktop.org/gstreamer/gst-build/ + +You may need to install the following packages using your package manager: + +json-glib, libsoup, libnice, libnice-gstreamer1 (the gstreamer plugin for libnice, called gstreamer1.0-nice Debian) + +### Ubuntu 18.04 + Here are the commands for Ubuntu 18.04. ``` sudo apt-get install -y gstreamer1.0-tools gstreamer1.0-nice gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good libgstreamer1.0-dev git libglib2.0-dev libgstreamer-plugins-bad1.0-dev libsoup2.4-dev libjson-glib-dev ``` -For hacking on the webrtc plugin, you may want to build manually using the git repositories: - - - http://cgit.freedesktop.org/gstreamer/gstreamer - - http://cgit.freedesktop.org/gstreamer/gst-plugins-base - - http://cgit.freedesktop.org/gstreamer/gst-plugins-good - - http://cgit.freedesktop.org/gstreamer/gst-plugins-bad - - http://cgit.freedesktop.org/libnice/libnice - -You can build these with either Autotools gst-uninstalled: - -https://arunraghavan.net/2014/07/quick-start-guide-to-gst-uninstalled-1-x/ - -Or with Meson gst-build: - -https://cgit.freedesktop.org/gstreamer/gst-build/ - -You may need to install the following packages using your package manager: - -json-glib, libsoup, libnice, libnice-gstreamer1 (the gstreamer plugin for libnice, called gstreamer1.0-nice Debian) - ## Filing bugs -Please only file bugs about the demos here. Bugs about GStreamer's WebRTC implementation should be filed on the [GStreamer bugzilla](https://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer&component=gst-plugins-bad). +Please only file bugs about the demos here. Bugs about GStreamer's WebRTC implementation should be filed on the [GStreamer gitlab](https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/new). You can also find us on IRC by joining #gstreamer @ FreeNode. @@ -58,6 +54,18 @@ http://blog.nirbheek.in/2018/02/gstreamer-webrtc.html ## Examples +### Building + +Most of the examples that require a build process can be built using the meson build system in the top-level gst-examples directory by using the following commands: + +```console +cd /path/to/gst-examples +meson _builddir +ninja -C _builddir +``` + +Build outputs will be placed in the directory `_builddir`. + ### sendrecv: Send and receive audio and video * Serve the `js/` directory on the root of your website, or open https://webrtc.nirbheek.in @@ -67,12 +75,6 @@ http://blog.nirbheek.in/2018/02/gstreamer-webrtc.html #### Running the C version -* Build the sources in the `gst/` directory on your machine. Use `make` or - -```console -$ gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o webrtc-sendrecv -``` - * Run `webrtc-sendrecv --peer-id=ID` with the `id` from the browser. You will see state changes and an SDP exchange. #### Running the Python version @@ -104,13 +106,7 @@ You can optionally specify the server URL too (it defaults to wss://webrtc.nirbh ### multiparty-sendrecv: Multiparty audio conference with N peers -* Build the sources in the `gst/` directory on your machine - -```console -$ gcc mp-webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o mp-webrtc-sendrecv -``` - -* Run `mp-webrtc-sendrecv --room-id=ID` with `ID` as a room name. The peer will connect to the signalling server and setup a conference room. +* Run `_builddir/multiparty-sendrecv/gst/mp-webrtc-sendrecv --room-id=ID` with `ID` as a room name. The peer will connect to the signalling server and setup a conference room. * Run this as many times as you like, each will spawn a peer that sends red noise and outputs the red noise it receives from other peers. - To change what a peer sends, find the `audiotestsrc` element in the source and change the `wave` property. - You can, of course, also replace `audiotestsrc` itself with `autoaudiosrc` (any platform) or `pulsesink` (on linux). diff --git a/webrtc/check/meson.build b/webrtc/check/meson.build index dfca6ebae7..a7f6456440 100644 --- a/webrtc/check/meson.build +++ b/webrtc/check/meson.build @@ -2,14 +2,17 @@ tests = [ ['basic', 'basic.py'], ] -test_deps = [certs] +if openssl.found() + test_deps = [certs] -foreach elem : tests - test(elem.get(0), - py3, - depends: test_deps, - args : files(elem.get(1)), - env : ['PYTHONPATH=' + join_paths(meson.source_root(), 'sendrecv', 'gst') + ':' + join_paths(meson.source_root(), 'signalling'), - 'TEST_HTML_SOURCE=' + join_paths(meson.source_root(), 'sendrecv', 'js'), - 'TEST_CA_CERT_PATH=' + join_paths(meson.build_root(), 'signalling')]) -endforeach + foreach elem : tests + test(elem.get(0), + py3, + depends: test_deps, + args : files(elem.get(1)), + # XXX: This PYTHONPATH hack is fun + env : ['PYTHONPATH=' + join_paths(meson.source_root(), 'sendrecv', 'gst') + ':' + join_paths(meson.source_root(), 'signalling'), + 'TEST_HTML_SOURCE=' + join_paths(meson.source_root(), 'sendrecv', 'js'), + 'TEST_CA_CERT_PATH=' + join_paths(meson.build_root(), 'signalling')]) + endforeach +endif diff --git a/webrtc/meson.build b/webrtc/meson.build index db32d48174..05fa6a4c82 100644 --- a/webrtc/meson.build +++ b/webrtc/meson.build @@ -1,31 +1,12 @@ -project('gstwebrtc-demo', 'c', - meson_version : '>= 0.48', - license: 'BSD-2-Clause', - default_options : [ 'warning_level=1', - 'buildtype=debug' ]) - -cc = meson.get_compiler('c') - -if cc.get_id() == 'msvc' - add_project_arguments( - cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8 - language : 'c') -endif - gst_req = '>= 1.14.0' -gst_dep = dependency('gstreamer-1.0', version : gst_req, - fallback : ['gstreamer', 'gst_dep']) gstsdp_dep = dependency('gstreamer-sdp-1.0', version : gst_req, fallback : ['gst-plugins-base', 'sdp_dep']) -gstwebrtc_dep = dependency('gstreamer-webrtc-1.0', version : gst_req, - fallback : ['gst-plugins-bad', 'gstwebrtc_dep']) libsoup_dep = dependency('libsoup-2.4', version : '>=2.48', fallback : ['libsoup', 'libsoup_dep']) json_glib_dep = dependency('json-glib-1.0', fallback : ['json-glib', 'json_glib_dep']) - py3_mod = import('python3') py3 = py3_mod.find_python() diff --git a/webrtc/signalling/generate_cert.sh b/webrtc/signalling/generate_cert.sh index 7f4084fffa..53745d5851 100755 --- a/webrtc/signalling/generate_cert.sh +++ b/webrtc/signalling/generate_cert.sh @@ -7,4 +7,10 @@ if [ $# -eq 1 ]; then OUTDIR=$1/ fi -openssl req -x509 -newkey rsa:4096 -keyout ${OUTDIR}key.pem -out ${OUTDIR}cert.pem -days 365 -nodes -subj "/CN=example.com" +output=$(openssl req -x509 -newkey rsa:4096 -keyout ${OUTDIR}key.pem -out ${OUTDIR}cert.pem -days 365 -nodes -subj "/CN=example.com" 2>&1) + +ret=$? +if [ ! $ret -eq 0 ]; then + echo "${output}" 1>&2 + exit $ret +fi diff --git a/webrtc/signalling/meson.build b/webrtc/signalling/meson.build index 43a53bfe73..76501c1c5e 100644 --- a/webrtc/signalling/meson.build +++ b/webrtc/signalling/meson.build @@ -1,8 +1,12 @@ -generate_certs = find_program('generate_cert.sh') -certs = custom_target( +simple_server = files('simple_server.py') + +openssl = find_program ('openssl', required : false) +if openssl.found() + # TODO: a better cert generation algorithm that doesn't rely on calling openssl + generate_certs = find_program('generate_cert.sh') + certs = custom_target( 'generate-certs', command: [generate_certs, '@OUTDIR@'], output : ['key.pem', 'cert.pem'] -) - -simple_server = files('simple_server.py') + ) +endif From 204945b902b43fc4f902786bebc0d68944f1dd36 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 19 Jun 2020 12:30:23 +1000 Subject: [PATCH 362/412] webrtc: indent sources Part-of: --- webrtc/android/app/src/main/jni/webrtc.c | 228 ++++++++++++----------- webrtc/sendonly/webrtc-recvonly-h264.c | 30 +-- webrtc/sendrecv/gst/webrtc-sendrecv.c | 5 +- 3 files changed, 140 insertions(+), 123 deletions(-) diff --git a/webrtc/android/app/src/main/jni/webrtc.c b/webrtc/android/app/src/main/jni/webrtc.c index 6bfd16a556..e5b47fc5fc 100644 --- a/webrtc/android/app/src/main/jni/webrtc.c +++ b/webrtc/android/app/src/main/jni/webrtc.c @@ -43,24 +43,25 @@ GST_DEBUG_CATEGORY_STATIC (debug_category); #define GET_CUSTOM_DATA(env, thiz, fieldID) (WebRTC *)(gintptr)(*env)->GetLongField (env, thiz, fieldID) #define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(gintptr)data) -enum AppState { - APP_STATE_UNKNOWN = 0, - APP_STATE_ERROR = 1, /* generic error */ - SERVER_CONNECTING = 1000, - SERVER_CONNECTION_ERROR, - SERVER_CONNECTED, /* Ready to register */ - SERVER_REGISTERING = 2000, - SERVER_REGISTRATION_ERROR, - SERVER_REGISTERED, /* Ready to call a peer */ - SERVER_CLOSED, /* server connection closed by us or the server */ - PEER_CONNECTING = 3000, - PEER_CONNECTION_ERROR, - PEER_CONNECTED, - PEER_CALL_NEGOTIATING = 4000, - PEER_CALL_STARTED, - PEER_CALL_STOPPING, - PEER_CALL_STOPPED, - PEER_CALL_ERROR, +enum AppState +{ + APP_STATE_UNKNOWN = 0, + APP_STATE_ERROR = 1, /* generic error */ + SERVER_CONNECTING = 1000, + SERVER_CONNECTION_ERROR, + SERVER_CONNECTED, /* Ready to register */ + SERVER_REGISTERING = 2000, + SERVER_REGISTRATION_ERROR, + SERVER_REGISTERED, /* Ready to call a peer */ + SERVER_CLOSED, /* server connection closed by us or the server */ + PEER_CONNECTING = 3000, + PEER_CONNECTION_ERROR, + PEER_CONNECTED, + PEER_CALL_NEGOTIATING = 4000, + PEER_CALL_STARTED, + PEER_CALL_STOPPING, + PEER_CALL_STOPPED, + PEER_CALL_ERROR, }; typedef struct _WebRTC @@ -115,7 +116,7 @@ cleanup_and_quit_loop (WebRTC * webrtc, const gchar * msg, enum AppState state) return G_SOURCE_REMOVE; } -static gchar* +static gchar * get_string_from_json_object (JsonObject * object) { JsonNode *root; @@ -135,8 +136,8 @@ get_string_from_json_object (JsonObject * object) } static GstElement * -handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, - const char * sink_name) +handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, + const char *sink_name) { GstPad *qpad; GstElement *q, *conv, *sink; @@ -176,14 +177,14 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, static void on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, - WebRTC * webrtc) + WebRTC * webrtc) { GstCaps *caps; const gchar *name; if (!gst_pad_has_current_caps (pad)) { g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", - GST_PAD_NAME (pad)); + GST_PAD_NAME (pad)); return; } @@ -191,11 +192,13 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, name = gst_structure_get_name (gst_caps_get_structure (caps, 0)); if (g_str_has_prefix (name, "video")) { - GstElement *sink = handle_media_stream (pad, webrtc->pipe, "videoconvert", "glimagesink"); + GstElement *sink = + handle_media_stream (pad, webrtc->pipe, "videoconvert", "glimagesink"); if (webrtc->video_sink == NULL) { webrtc->video_sink = sink; if (webrtc->native_window) - gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), (gpointer) webrtc->native_window); + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), + (gpointer) webrtc->native_window); } } else if (g_str_has_prefix (name, "audio")) { handle_media_stream (pad, webrtc->pipe, "audioconvert", "autoaudiosink"); @@ -216,21 +219,22 @@ on_incoming_stream (GstElement * webrtcbin, GstPad * pad, WebRTC * webrtc) decodebin = gst_element_factory_make ("decodebin", NULL); g_signal_connect (decodebin, "pad-added", - G_CALLBACK (on_incoming_decodebin_stream), webrtc); + G_CALLBACK (on_incoming_decodebin_stream), webrtc); gst_bin_add (GST_BIN (webrtc->pipe), decodebin); gst_element_sync_state_with_parent (decodebin); gst_element_link (webrtcbin, decodebin); } static void -send_ice_candidate_message (GstElement * webrtcbin G_GNUC_UNUSED, guint mlineindex, - gchar * candidate, WebRTC * webrtc) +send_ice_candidate_message (GstElement * webrtcbin G_GNUC_UNUSED, + guint mlineindex, gchar * candidate, WebRTC * webrtc) { gchar *text; JsonObject *ice, *msg; if (webrtc->app_state < PEER_CALL_NEGOTIATING) { - cleanup_and_quit_loop (webrtc, "Can't send ICE, not in call", APP_STATE_ERROR); + cleanup_and_quit_loop (webrtc, "Can't send ICE, not in call", + APP_STATE_ERROR); return; } @@ -253,7 +257,8 @@ send_sdp_offer (WebRTC * webrtc, GstWebRTCSessionDescription * offer) JsonObject *msg, *sdp; if (webrtc->app_state < PEER_CALL_NEGOTIATING) { - cleanup_and_quit_loop (webrtc, "Can't send offer, not in call", APP_STATE_ERROR); + cleanup_and_quit_loop (webrtc, "Can't send offer, not in call", + APP_STATE_ERROR); return; } @@ -283,14 +288,15 @@ on_offer_created (GstPromise * promise, WebRTC * webrtc) g_assert (webrtc->app_state == PEER_CALL_NEGOTIATING); - g_assert (gst_promise_wait(promise) == GST_PROMISE_RESULT_REPLIED); + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); reply = gst_promise_get_reply (promise); gst_structure_get (reply, "offer", - GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); gst_promise_unref (promise); promise = gst_promise_new (); - g_signal_emit_by_name (webrtc->webrtcbin, "set-local-description", offer, promise); + g_signal_emit_by_name (webrtc->webrtcbin, "set-local-description", offer, + promise); gst_promise_interrupt (promise); gst_promise_unref (promise); @@ -333,13 +339,12 @@ start_pipeline (WebRTC * webrtc) GstPad *pad; webrtc->pipe = - gst_parse_launch ("webrtcbin name=sendrecv " - "ahcsrc device-facing=front ! video/x-raw,width=[320,1280] ! queue max-size-buffers=1 ! videoconvert ! " - "vp8enc keyframe-max-dist=30 deadline=1 error-resilient=default ! rtpvp8pay picture-id-mode=15-bit mtu=1300 ! " - "queue max-size-time=300000000 ! " RTP_CAPS_VP8 " ! sendrecv.sink_0 " - "openslessrc ! queue ! audioconvert ! audioresample ! audiorate ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS " ! sendrecv.sink_1 ", - &error); + gst_parse_launch ("webrtcbin name=sendrecv " + "ahcsrc device-facing=front ! video/x-raw,width=[320,1280] ! queue max-size-buffers=1 ! videoconvert ! " + "vp8enc keyframe-max-dist=30 deadline=1 error-resilient=default ! rtpvp8pay picture-id-mode=15-bit mtu=1300 ! " + "queue max-size-time=300000000 ! " RTP_CAPS_VP8 " ! sendrecv.sink_0 " + "openslessrc ! queue ! audioconvert ! audioresample ! audiorate ! queue ! opusenc ! rtpopuspay ! " + "queue ! " RTP_CAPS_OPUS " ! sendrecv.sink_1 ", &error); if (error) { g_printerr ("Failed to parse launch: %s\n", error->message); @@ -354,15 +359,15 @@ start_pipeline (WebRTC * webrtc) /* This is the gstwebrtc entry point where we create the offer and so on. It * will be called when the pipeline goes to PLAYING. */ g_signal_connect (webrtc->webrtcbin, "on-negotiation-needed", - G_CALLBACK (on_negotiation_needed), webrtc); + G_CALLBACK (on_negotiation_needed), webrtc); /* We need to transmit this ICE candidate to the browser via the websockets * signalling server. Incoming ice candidates from the browser need to be * added by us too, see on_server_message() */ g_signal_connect (webrtc->webrtcbin, "on-ice-candidate", - G_CALLBACK (send_ice_candidate_message), webrtc); + G_CALLBACK (send_ice_candidate_message), webrtc); /* Incoming streams will be exposed via this signal */ - g_signal_connect (webrtc->webrtcbin, "pad-added", G_CALLBACK (on_incoming_stream), - webrtc); + g_signal_connect (webrtc->webrtcbin, "pad-added", + G_CALLBACK (on_incoming_stream), webrtc); /* Lifetime is the same as the pipeline itself */ gst_object_unref (webrtc->webrtcbin); @@ -425,8 +430,7 @@ register_with_server (WebRTC * webrtc) } static void -on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, - WebRTC * webrtc) +on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, WebRTC * webrtc) { webrtc->app_state = SERVER_CLOSED; cleanup_and_quit_loop (webrtc, "Server connection closed", 0); @@ -435,7 +439,7 @@ on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, /* One mega message handler for our asynchronous calling mechanism */ static void on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, - GBytes * message, WebRTC * webrtc) + GBytes * message, WebRTC * webrtc) { gsize size; gchar *text, *data; @@ -443,14 +447,14 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, switch (type) { case SOUP_WEBSOCKET_DATA_BINARY: g_printerr ("Received unknown binary message, ignoring\n"); - g_bytes_unref (message); - return; + g_bytes_unref (message); + return; case SOUP_WEBSOCKET_DATA_TEXT: data = g_bytes_unref_to_data (message, &size); - /* Convert to NULL-terminated string */ - text = g_strndup (data, size); - g_free (data); - break; + /* Convert to NULL-terminated string */ + text = g_strndup (data, size); + g_free (data); + break; default: g_assert_not_reached (); } @@ -458,22 +462,23 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, /* Server has accepted our registration, we are ready to send commands */ if (g_strcmp0 (text, "HELLO") == 0) { if (webrtc->app_state != SERVER_REGISTERING) { - cleanup_and_quit_loop (webrtc, "ERROR: Received HELLO when not registering", - APP_STATE_ERROR); + cleanup_and_quit_loop (webrtc, + "ERROR: Received HELLO when not registering", APP_STATE_ERROR); goto out; } webrtc->app_state = SERVER_REGISTERED; g_print ("Registered with server\n"); /* Ask signalling server to connect us with a specific peer */ if (!setup_call (webrtc)) { - cleanup_and_quit_loop (webrtc, "ERROR: Failed to setup call", PEER_CALL_ERROR); + cleanup_and_quit_loop (webrtc, "ERROR: Failed to setup call", + PEER_CALL_ERROR); goto out; } /* Call has been setup by the server, now we can start negotiation */ } else if (g_strcmp0 (text, "SESSION_OK") == 0) { if (webrtc->app_state != PEER_CONNECTING) { - cleanup_and_quit_loop (webrtc, "ERROR: Received SESSION_OK when not calling", - PEER_CONNECTION_ERROR); + cleanup_and_quit_loop (webrtc, + "ERROR: Received SESSION_OK when not calling", PEER_CONNECTION_ERROR); goto out; } @@ -481,23 +486,23 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, /* Start negotiation (exchange SDP and ICE candidates) */ if (!start_pipeline (webrtc)) cleanup_and_quit_loop (webrtc, "ERROR: failed to start pipeline", - PEER_CALL_ERROR); + PEER_CALL_ERROR); /* Handle errors */ } else if (g_str_has_prefix (text, "ERROR")) { switch (webrtc->app_state) { case SERVER_CONNECTING: webrtc->app_state = SERVER_CONNECTION_ERROR; - break; + break; case SERVER_REGISTERING: webrtc->app_state = SERVER_REGISTRATION_ERROR; - break; + break; case PEER_CONNECTING: webrtc->app_state = PEER_CONNECTION_ERROR; - break; + break; case PEER_CONNECTED: case PEER_CALL_NEGOTIATING: webrtc->app_state = PEER_CALL_ERROR; - break; + break; default: webrtc->app_state = APP_STATE_ERROR; } @@ -541,7 +546,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, * 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 (json_object_get_string_member (object, "type"), ==, - "answer"); + "answer"); text = json_object_get_string_member (object, "sdp"); @@ -554,14 +559,14 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_assert (ret == GST_SDP_OK); answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, - sdp); + sdp); g_assert (answer); /* Set remote description on our pipeline */ { GstPromise *promise = gst_promise_new (); - g_signal_emit_by_name (webrtc->webrtcbin, "set-remote-description", answer, - promise); + g_signal_emit_by_name (webrtc->webrtcbin, "set-remote-description", + answer, promise); gst_promise_interrupt (promise); gst_promise_unref (promise); } @@ -577,25 +582,25 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, sdpmlineindex = json_object_get_int_member (ice, "sdpMLineIndex"); /* Add ice candidate sent by remote peer */ - g_signal_emit_by_name (webrtc->webrtcbin, "add-ice-candidate", sdpmlineindex, - candidate); + g_signal_emit_by_name (webrtc->webrtcbin, "add-ice-candidate", + sdpmlineindex, candidate); } else { g_printerr ("Ignoring unknown JSON message:\n%s\n", text); } g_object_unref (parser); } - out: +out: g_free (text); } static void -on_server_connected (SoupSession * session, GAsyncResult * res, - WebRTC * webrtc) +on_server_connected (SoupSession * session, GAsyncResult * res, WebRTC * webrtc) { GError *error = NULL; - webrtc->ws_conn = soup_session_websocket_connect_finish (session, res, &error); + webrtc->ws_conn = + soup_session_websocket_connect_finish (session, res, &error); if (error) { cleanup_and_quit_loop (webrtc, error->message, SERVER_CONNECTION_ERROR); g_error_free (error); @@ -607,8 +612,10 @@ on_server_connected (SoupSession * session, GAsyncResult * res, webrtc->app_state = SERVER_CONNECTED; g_print ("Connected to signalling server\n"); - g_signal_connect (webrtc->ws_conn, "closed", G_CALLBACK (on_server_closed), webrtc); - g_signal_connect (webrtc->ws_conn, "message", G_CALLBACK (on_server_message), webrtc); + g_signal_connect (webrtc->ws_conn, "closed", G_CALLBACK (on_server_closed), + webrtc); + g_signal_connect (webrtc->ws_conn, "message", G_CALLBACK (on_server_message), + webrtc); /* Register with the server so it knows about us and can accept commands */ register_with_server (webrtc); @@ -623,16 +630,16 @@ connect_to_websocket_server_async (WebRTC * webrtc) SoupLogger *logger; SoupMessage *message; SoupSession *session; - const char *https_aliases[] = {"wss", NULL}; + const char *https_aliases[] = { "wss", NULL }; const gchar *ca_certs; - ca_certs = g_getenv("CA_CERTIFICATES"); + ca_certs = g_getenv ("CA_CERTIFICATES"); g_assert (ca_certs != NULL); g_print ("ca-certificates %s", ca_certs); session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, FALSE, - // SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_SSL_CA_FILE, ca_certs, - SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); + // SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + SOUP_SESSION_SSL_CA_FILE, ca_certs, + SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); @@ -644,7 +651,7 @@ connect_to_websocket_server_async (WebRTC * webrtc) /* Once connected, we will register */ soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, - (GAsyncReadyCallback) on_server_connected, webrtc); + (GAsyncReadyCallback) on_server_connected, webrtc); webrtc->app_state = SERVER_CONNECTING; return G_SOURCE_REMOVE; @@ -708,7 +715,7 @@ native_end_call (JNIEnv * env, jobject thiz) if (webrtc->loop) { GThread *thread = webrtc->thread; - GST_INFO("Ending current call"); + GST_INFO ("Ending current call"); cleanup_and_quit_loop (webrtc, NULL, 0); webrtc->thread = NULL; g_mutex_unlock (&webrtc->lock); @@ -729,14 +736,15 @@ static gpointer _call_thread (WebRTC * webrtc) { GMainContext *context = NULL; - JNIEnv *env = attach_current_thread(); + JNIEnv *env = attach_current_thread (); g_mutex_lock (&webrtc->lock); context = g_main_context_new (); webrtc->loop = g_main_loop_new (context, FALSE); g_main_context_invoke (context, (GSourceFunc) _unlock_mutex, &webrtc->lock); - g_main_context_invoke (context, (GSourceFunc) connect_to_websocket_server_async, webrtc); + g_main_context_invoke (context, + (GSourceFunc) connect_to_websocket_server_async, webrtc); g_main_context_push_thread_default (context); g_cond_broadcast (&webrtc->cond); g_main_loop_run (webrtc->loop); @@ -748,7 +756,7 @@ _call_thread (WebRTC * webrtc) } static void -native_call_other_party(JNIEnv * env, jobject thiz) +native_call_other_party (JNIEnv * env, jobject thiz) { WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); @@ -758,9 +766,9 @@ native_call_other_party(JNIEnv * env, jobject thiz) if (webrtc->thread) native_end_call (env, thiz); - GST_INFO("calling other party"); + GST_INFO ("calling other party"); - webrtc->thread = g_thread_new("webrtc", (GThreadFunc) _call_thread, webrtc); + webrtc->thread = g_thread_new ("webrtc", (GThreadFunc) _call_thread, webrtc); g_mutex_lock (&webrtc->lock); while (!webrtc->loop) g_cond_wait (&webrtc->cond, &webrtc->lock); @@ -814,14 +822,13 @@ native_class_init (JNIEnv * env, jclass klass) __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message); (*env)->ThrowNew (env, exception_class, message); } - //gst_debug_set_threshold_from_string ("gl*:7", FALSE); } static void native_set_surface (JNIEnv * env, jobject thiz, jobject surface) { - WebRTC *webrtc= GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); + WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); ANativeWindow *new_native_window; if (!webrtc) @@ -829,7 +836,7 @@ native_set_surface (JNIEnv * env, jobject thiz, jobject surface) new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL; GST_DEBUG ("Received surface %p (native window %p)", surface, - new_native_window); + new_native_window); if (webrtc->native_window) { ANativeWindow_release (webrtc->native_window); @@ -837,36 +844,39 @@ native_set_surface (JNIEnv * env, jobject thiz, jobject surface) webrtc->native_window = new_native_window; if (webrtc->video_sink) - gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (webrtc->video_sink), (guintptr) new_native_window); + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (webrtc->video_sink), + (guintptr) new_native_window); } static void -native_set_signalling_server (JNIEnv * env, jobject thiz, jstring server) { - WebRTC *webrtc= GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); - const gchar *s; - - if (!webrtc) - return; - - s = (*env)->GetStringUTFChars(env, server, NULL); - if (webrtc->signalling_server) - g_free (webrtc->signalling_server); - webrtc->signalling_server = g_strdup (s); - (*env)->ReleaseStringUTFChars(env, server, s); -} - -static void -native_set_call_id(JNIEnv * env, jobject thiz, jstring peer_id) { +native_set_signalling_server (JNIEnv * env, jobject thiz, jstring server) +{ WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); const gchar *s; if (!webrtc) return; - s = (*env)->GetStringUTFChars(env, peer_id, NULL); + s = (*env)->GetStringUTFChars (env, server, NULL); + if (webrtc->signalling_server) + g_free (webrtc->signalling_server); + webrtc->signalling_server = g_strdup (s); + (*env)->ReleaseStringUTFChars (env, server, s); +} + +static void +native_set_call_id (JNIEnv * env, jobject thiz, jstring peer_id) +{ + WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id); + const gchar *s; + + if (!webrtc) + return; + + s = (*env)->GetStringUTFChars (env, peer_id, NULL); g_free (webrtc->peer_id); webrtc->peer_id = g_strdup (s); - (*env)->ReleaseStringUTFChars(env, peer_id, s); + (*env)->ReleaseStringUTFChars (env, peer_id, s); } /* List of implemented native methods */ diff --git a/webrtc/sendonly/webrtc-recvonly-h264.c b/webrtc/sendonly/webrtc-recvonly-h264.c index 87b24f2f26..5a5508afc2 100644 --- a/webrtc/sendonly/webrtc-recvonly-h264.c +++ b/webrtc/sendonly/webrtc-recvonly-h264.c @@ -183,8 +183,8 @@ const gchar *html_source = " \n \ "; static void -handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, - const char * sink_name) +handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, + const char *sink_name) { GstPad *qpad; GstElement *q, *conv, *resample, *sink; @@ -250,7 +250,8 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, } static void -on_incoming_stream (GstElement * webrtc, GstPad * pad, ReceiverEntry *receiver_entry) +on_incoming_stream (GstElement * webrtc, GstPad * pad, + ReceiverEntry * receiver_entry) { GstElement *decodebin; GstPad *sinkpad; @@ -287,10 +288,11 @@ create_receiver_entry (SoupWebsocketConnection * connection) G_CALLBACK (soup_websocket_message_cb), (gpointer) receiver_entry); error = NULL; - receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " + receiver_entry->pipeline = + gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" + STUN_SERVER " " "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! webrtcbin. " - , &error); + "queue ! " RTP_CAPS_OPUS "97 ! webrtcbin. ", &error); if (error != NULL) { g_error ("Could not create WebRTC pipeline: %s\n", error->message); g_error_free (error); @@ -302,18 +304,24 @@ create_receiver_entry (SoupWebsocketConnection * connection) g_assert (receiver_entry->webrtcbin != NULL); /* Incoming streams will be exposed via this signal */ - g_signal_connect (receiver_entry->webrtcbin, "pad-added", G_CALLBACK (on_incoming_stream), - receiver_entry); + g_signal_connect (receiver_entry->webrtcbin, "pad-added", + G_CALLBACK (on_incoming_stream), receiver_entry); #if 0 - GstElement *rtpbin = gst_bin_get_by_name (GST_BIN (receiver_entry->webrtcbin), "rtpbin"); + GstElement *rtpbin = + gst_bin_get_by_name (GST_BIN (receiver_entry->webrtcbin), "rtpbin"); g_object_set (rtpbin, "latency", 40, NULL); gst_object_unref (rtpbin); #endif // Create a 2nd transceiver for the receive only video stream - video_caps = gst_caps_from_string ("application/x-rtp,media=video,encoding-name=H264,payload=" RTP_PAYLOAD_TYPE ",clock-rate=90000,packetization-mode=(string)1, profile-level-id=(string)42c016"); - g_signal_emit_by_name (receiver_entry->webrtcbin, "add-transceiver", GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, video_caps, NULL, &trans); + video_caps = + gst_caps_from_string + ("application/x-rtp,media=video,encoding-name=H264,payload=" + RTP_PAYLOAD_TYPE + ",clock-rate=90000,packetization-mode=(string)1, profile-level-id=(string)42c016"); + g_signal_emit_by_name (receiver_entry->webrtcbin, "add-transceiver", + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, video_caps, NULL, &trans); gst_caps_unref (video_caps); gst_object_unref (trans); diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 956b92b95a..4453c954da 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -525,7 +525,7 @@ on_offer_set (GstPromise * promise, gpointer user_data) } static void -on_offer_received (GstSDPMessage *sdp) +on_offer_received (GstSDPMessage * sdp) { GstWebRTCSessionDescription *offer = NULL; GstPromise *promise; @@ -536,8 +536,7 @@ on_offer_received (GstSDPMessage *sdp) /* Set remote description on our pipeline */ { promise = gst_promise_new_with_change_func (on_offer_set, NULL, NULL); - g_signal_emit_by_name (webrtc1, "set-remote-description", offer, - promise); + g_signal_emit_by_name (webrtc1, "set-remote-description", offer, promise); } gst_webrtc_session_description_free (offer); } From f5d94716397ad3a0780a1e6b70524269fce47c46 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Thu, 25 Jun 2020 22:11:33 +1000 Subject: [PATCH 363/412] webrtc/test: check if selenium is available before attempting to add tests Fixes the following error File "/builds/vivia/gst-plugins-bad/gst-build/build/../subprojects/gst-examples/webrtc/check/basic.py", line 5, in from selenium import webdriver ModuleNotFoundError: No module named 'selenium' Part-of: --- webrtc/check/configure_test_check.py | 15 +++++++++++++++ webrtc/check/meson.build | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 webrtc/check/configure_test_check.py diff --git a/webrtc/check/configure_test_check.py b/webrtc/check/configure_test_check.py new file mode 100644 index 0000000000..d7d56ddf0e --- /dev/null +++ b/webrtc/check/configure_test_check.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import os +import unittest +from selenium import webdriver +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from selenium.webdriver.chrome.options import Options as COptions +import asyncio +import threading +import signal + +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst diff --git a/webrtc/check/meson.build b/webrtc/check/meson.build index a7f6456440..15fc8bc15b 100644 --- a/webrtc/check/meson.build +++ b/webrtc/check/meson.build @@ -2,8 +2,12 @@ tests = [ ['basic', 'basic.py'], ] -if openssl.found() +check_test = find_program ('configure_test_check.py') +have_webrtc_check_deps = run_command (check_test).returncode() == 0 + +if openssl.found() and have_webrtc_check_deps test_deps = [certs] + message ('adding webrtc tests') foreach elem : tests test(elem.get(0), From 234dff8dbb655dd7fa7bfc4ee8167e076f8640b9 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Mon, 29 Jun 2020 14:08:51 +0100 Subject: [PATCH 364/412] webrtc: Add Janus video-room example This Rust crate provides a program able to connect to a Janus instance using WebSockets and send a live video stream to the videoroom plugin. Part-of: --- webrtc/janus/rust/Cargo.lock | 1099 ++++++++++++++++++++++++++++++++ webrtc/janus/rust/Cargo.toml | 25 + webrtc/janus/rust/src/janus.rs | 707 ++++++++++++++++++++ webrtc/janus/rust/src/main.rs | 186 ++++++ 4 files changed, 2017 insertions(+) create mode 100644 webrtc/janus/rust/Cargo.lock create mode 100644 webrtc/janus/rust/Cargo.toml create mode 100644 webrtc/janus/rust/src/janus.rs create mode 100644 webrtc/janus/rust/src/main.rs diff --git a/webrtc/janus/rust/Cargo.lock b/webrtc/janus/rust/Cargo.lock new file mode 100644 index 0000000000..3cde9a5a99 --- /dev/null +++ b/webrtc/janus/rust/Cargo.lock @@ -0,0 +1,1099 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" + +[[package]] +name = "async-tungstenite" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c35882200d230428ae9cf74acb72aaa9c0a912911b4dad8fb890da49608e127" +dependencies = [ + "futures-io", + "futures-util", + "gio", + "glib", + "log", + "pin-project", + "tungstenite", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" +dependencies = [ + "loom", +] + +[[package]] +name = "cc" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1be3409f94d7bdceeb5f5fac551039d9b3f00e25da7a74fc4d33400a0d96368" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-executor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generator" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" +dependencies = [ + "cc", + "libc", + "log", + "rustc_version", + "winapi", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gio" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd10f9415cce39b53f8024bf39a21f84f8157afa52da53837b102e585a296a5" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "lazy_static", + "libc", +] + +[[package]] +name = "gio-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fad225242b9eae7ec8a063bb86974aca56885014672375e5775dc0ea3533911" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", +] + +[[package]] +name = "glib" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "glib-sys", + "gobject-sys", + "lazy_static", + "libc", +] + +[[package]] +name = "glib-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "gobject-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", +] + +[[package]] +name = "gstreamer" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8664a114cd6ec16bece783d5eee59496919915b1f6884400ba4a953274a163" +dependencies = [ + "bitflags", + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "lazy_static", + "libc", + "muldiv", + "num-rational", + "paste", +] + +[[package]] +name = "gstreamer-sdp" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547b3b0eb9e01e13ab5cc066c817e2ab758f83790145f80f62d3c8e43c2966af" +dependencies = [ + "glib", + "glib-sys", + "gobject-sys", + "gstreamer", + "gstreamer-sdp-sys", + "gstreamer-sys", +] + +[[package]] +name = "gstreamer-sdp-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "pkg-config", +] + +[[package]] +name = "gstreamer-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", +] + +[[package]] +name = "gstreamer-webrtc" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" +dependencies = [ + "glib", + "glib-sys", + "gobject-sys", + "gstreamer", + "gstreamer-sdp", + "gstreamer-sys", + "gstreamer-webrtc-sys", + "libc", +] + +[[package]] +name = "gstreamer-webrtc-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sdp-sys", + "gstreamer-sys", + "libc", + "pkg-config", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "janus-video-room" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-tungstenite", + "env_logger", + "futures", + "gio", + "glib", + "gstreamer", + "gstreamer-sdp", + "gstreamer-webrtc", + "http", + "log", + "rand", + "serde", + "serde_derive", + "serde_json", + "structopt", + "url", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "muldiv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "proc-macro-error" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scoped-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "structopt" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "tungstenite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log", + "rand", + "sha-1", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/webrtc/janus/rust/Cargo.toml b/webrtc/janus/rust/Cargo.toml new file mode 100644 index 0000000000..29c6aad29d --- /dev/null +++ b/webrtc/janus/rust/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "janus-video-room" +version = "0.1.0" +authors = ["Philippe Normand "] +edition = "2018" +license = "LGPL" + +[dependencies] +futures = "0.3" +structopt = { version = "0.3", default-features = false } +anyhow = "1" +url = "2" +rand = "0.7" +async-tungstenite = { version = "0.7", features = ["gio-runtime"] } +gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } +gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } +gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } +serde = "1" +serde_derive = "1" +serde_json = "1.0.53" +http = "0.2" +glib = "0.9" +gio = "0.8" +log = "0.4.8" +env_logger = "0.7.1" diff --git a/webrtc/janus/rust/src/janus.rs b/webrtc/janus/rust/src/janus.rs new file mode 100644 index 0000000000..e37adf80b2 --- /dev/null +++ b/webrtc/janus/rust/src/janus.rs @@ -0,0 +1,707 @@ +// GStreamer +// +// Copyright (C) 2018 maxmcd +// Copyright (C) 2019 Sebastian Dröge +// Copyright (C) 2020 Philippe Normand +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301, USA. + +use { + anyhow::{anyhow, bail, Context}, + async_tungstenite::{gio::connect_async, tungstenite}, + futures::channel::mpsc, + futures::sink::{Sink, SinkExt}, + futures::stream::{Stream, StreamExt}, + gst::gst_element_error, + gst::prelude::*, + http::Request, + rand::prelude::*, + serde_derive::{Deserialize, Serialize}, + serde_json::json, + std::sync::{Arc, Mutex, Weak}, + structopt::StructOpt, + tungstenite::Message as WsMessage, +}; + +// upgrade weak reference or return +#[macro_export] +macro_rules! upgrade_weak { + ($x:ident, $r:expr) => {{ + match $x.upgrade() { + Some(o) => o, + None => return $r, + } + }}; + ($x:ident) => { + upgrade_weak!($x, ()) + }; +} + +#[derive(Debug)] +struct VideoParameter { + encoder: &'static str, + encoding_name: &'static str, + payloader: &'static str, +} + +const VP8: VideoParameter = VideoParameter { + encoder: "vp8enc target-bitrate=100000 overshoot=25 undershoot=100 deadline=33000 keyframe-max-dist=1", + encoding_name: "VP8", + payloader: "rtpvp8pay picture-id-mode=2" +}; + +const H264: VideoParameter = VideoParameter { + encoder: "x264enc tune=zerolatency", + encoding_name: "H264", + payloader: "rtph264pay", +}; + +impl std::str::FromStr for VideoParameter { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "vp8" => Ok(VP8), + "h264" => Ok(H264), + _ => Err(anyhow!( + "Invalid video parameter: {}. Use either vp8 or h264", + s + )), + } + } +} + +#[derive(Debug, StructOpt)] +pub struct Args { + #[structopt(short, long, default_value = "wss://janus.conf.meetecho.com/ws:8989")] + server: String, + #[structopt(short, long, default_value = "1234")] + room_id: u32, + #[structopt(short, long, default_value = "1234")] + feed_id: u32, + #[structopt(short, long, default_value = "vp8")] + webrtc_video_codec: VideoParameter, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Base { + janus: String, + transaction: Option, + session_id: Option, + sender: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct DataHolder { + id: i64, +} + +#[derive(Serialize, Deserialize, Debug)] +struct PluginDataHolder { + videoroom: String, + room: i64, + description: Option, + id: Option, + configured: Option, + video_codec: Option, + unpublished: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct PluginHolder { + plugin: String, + data: PluginDataHolder, +} + +#[derive(Serialize, Deserialize, Debug)] +struct IceHolder { + candidate: String, + #[serde(rename = "sdpMLineIndex")] + sdp_mline_index: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +struct JsepHolder { + #[serde(rename = "type")] + type_: String, + sdp: Option, + ice: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct JsonReply { + #[serde(flatten)] + base: Base, + data: Option, + #[serde(rename = "plugindata")] + plugin_data: Option, + jsep: Option, +} + +fn transaction_id() -> String { + thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(30) + .collect() +} + +// Strong reference to the state of one peer +#[derive(Debug, Clone)] +struct Peer(Arc); + +// Weak reference to the state of one peer +#[derive(Debug, Clone)] +struct PeerWeak(Weak); + +impl PeerWeak { + // Try upgrading a weak reference to a strong one + fn upgrade(&self) -> Option { + self.0.upgrade().map(Peer) + } +} + +// To be able to access the Peers's fields directly +impl std::ops::Deref for Peer { + type Target = PeerInner; + + fn deref(&self) -> &PeerInner { + &self.0 + } +} + +#[derive(Clone, Copy, Debug)] +struct ConnectionHandle { + id: i64, + session_id: i64, +} + +// Actual peer state +#[derive(Debug)] +struct PeerInner { + handle: ConnectionHandle, + bin: gst::Bin, + webrtcbin: gst::Element, + send_msg_tx: Arc>>, +} + +impl Peer { + // Downgrade the strong reference to a weak reference + fn downgrade(&self) -> PeerWeak { + PeerWeak(Arc::downgrade(&self.0)) + } + + // Whenever webrtcbin tells us that (re-)negotiation is needed, simply ask + // for a new offer SDP from webrtcbin without any customization and then + // asynchronously send it to the peer via the WebSocket connection + fn on_negotiation_needed(&self) -> Result<(), anyhow::Error> { + info!("starting negotiation with peer"); + + let peer_clone = self.downgrade(); + let promise = gst::Promise::new_with_change_func(move |res| { + let s = res.expect("no answer"); + let peer = upgrade_weak!(peer_clone); + + if let Err(err) = peer.on_offer_created(&s.to_owned()) { + gst_element_error!( + peer.bin, + gst::LibraryError::Failed, + ("Failed to send SDP offer: {:?}", err) + ); + } + }); + + self.webrtcbin + .emit("create-offer", &[&None::, &promise])?; + + Ok(()) + } + + // Once webrtcbin has create the offer SDP for us, handle it by sending it to the peer via the + // WebSocket connection + fn on_offer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> { + let offer = reply + .get_value("offer")? + .get::() + .expect("Invalid argument") + .expect("Invalid offer"); + self.webrtcbin + .emit("set-local-description", &[&offer, &None::])?; + + info!("sending SDP offer to peer: {:?}", offer.get_sdp().as_text()); + + let transaction = transaction_id(); + let sdp_data = offer.get_sdp().as_text()?; + let msg = WsMessage::Text( + json!({ + "janus": "message", + "transaction": transaction, + "session_id": self.handle.session_id, + "handle_id": self.handle.id, + "body": { + "request": "publish", + "audio": true, + "video": true, + }, + "jsep": { + "sdp": sdp_data, + "trickle": true, + "type": "offer" + } + }) + .to_string(), + ); + self.send_msg_tx + .lock() + .expect("Invalid message sender") + .unbounded_send(msg) + .with_context(|| "Failed to send SDP offer".to_string())?; + + Ok(()) + } + + // Once webrtcbin has create the answer SDP for us, handle it by sending it to the peer via the + // WebSocket connection + fn on_answer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> { + let answer = reply + .get_value("answer")? + .get::() + .expect("Invalid argument") + .expect("Invalid answer"); + self.webrtcbin + .emit("set-local-description", &[&answer, &None::])?; + + info!( + "sending SDP answer to peer: {:?}", + answer.get_sdp().as_text() + ); + + Ok(()) + } + + // Handle incoming SDP answers from the peer + fn handle_sdp(&self, type_: &str, sdp: &str) -> Result<(), anyhow::Error> { + if type_ == "answer" { + info!("Received answer:\n{}\n", sdp); + + let ret = gst_sdp::SDPMessage::parse_buffer(sdp.as_bytes()) + .map_err(|_| anyhow!("Failed to parse SDP answer"))?; + let answer = + gst_webrtc::WebRTCSessionDescription::new(gst_webrtc::WebRTCSDPType::Answer, ret); + + self.webrtcbin + .emit("set-remote-description", &[&answer, &None::])?; + + Ok(()) + } else if type_ == "offer" { + info!("Received offer:\n{}\n", sdp); + + let ret = gst_sdp::SDPMessage::parse_buffer(sdp.as_bytes()) + .map_err(|_| anyhow!("Failed to parse SDP offer"))?; + + // And then asynchronously start our pipeline and do the next steps. The + // pipeline needs to be started before we can create an answer + let peer_clone = self.downgrade(); + self.bin.call_async(move |_pipeline| { + let peer = upgrade_weak!(peer_clone); + + let offer = gst_webrtc::WebRTCSessionDescription::new( + gst_webrtc::WebRTCSDPType::Offer, + ret, + ); + + peer.0 + .webrtcbin + .emit("set-remote-description", &[&offer, &None::]) + .expect("Unable to set remote description"); + + let peer_clone = peer.downgrade(); + let promise = gst::Promise::new_with_change_func(move |reply| { + let s = reply.expect("No answer"); + let peer = upgrade_weak!(peer_clone); + + if let Err(err) = peer.on_answer_created(&s.to_owned()) { + gst_element_error!( + peer.bin, + gst::LibraryError::Failed, + ("Failed to send SDP answer: {:?}", err) + ); + } + }); + + peer.0 + .webrtcbin + .emit("create-answer", &[&None::, &promise]) + .expect("Unable to create answer"); + }); + + Ok(()) + } else { + bail!("Sdp type is not \"answer\" but \"{}\"", type_) + } + } + + // Handle incoming ICE candidates from the peer by passing them to webrtcbin + fn handle_ice(&self, sdp_mline_index: u32, candidate: &str) -> Result<(), anyhow::Error> { + info!( + "Received remote ice-candidate {} {}", + sdp_mline_index, candidate + ); + self.webrtcbin + .emit("add-ice-candidate", &[&sdp_mline_index, &candidate])?; + + Ok(()) + } + + // Asynchronously send ICE candidates to the peer via the WebSocket connection as a JSON + // message + fn on_ice_candidate(&self, mlineindex: u32, candidate: String) -> Result<(), anyhow::Error> { + let transaction = transaction_id(); + info!("Sending ICE {} {}", mlineindex, &candidate); + let msg = WsMessage::Text( + json!({ + "janus": "trickle", + "transaction": transaction, + "session_id": self.handle.session_id, + "handle_id": self.handle.id, + "candidate": { + "candidate": candidate, + "sdpMLineIndex": mlineindex + }, + }) + .to_string(), + ); + self.send_msg_tx + .lock() + .expect("Invalid message sender") + .unbounded_send(msg) + .with_context(|| "Failed to send ICE candidate".to_string())?; + + Ok(()) + } +} + +// At least shut down the bin here if it didn't happen so far +impl Drop for PeerInner { + fn drop(&mut self) { + let _ = self.bin.set_state(gst::State::Null); + } +} + +type WsStream = + std::pin::Pin> + Send>>; +type WsSink = std::pin::Pin + Send>>; + +pub struct JanusGateway { + ws_stream: Option, + ws_sink: Option, + handle: ConnectionHandle, + peer: Mutex, + send_ws_msg_rx: Option>, +} + +impl JanusGateway { + pub async fn new(pipeline: gst::Bin) -> Result { + let args = Args::from_args(); + let request = Request::builder() + .uri(&args.server) + .header("Sec-WebSocket-Protocol", "janus-protocol") + .body(())?; + + let (mut ws, _) = connect_async(request).await?; + + let transaction = transaction_id(); + let msg = WsMessage::Text( + json!({ + "janus": "create", + "transaction": transaction, + }) + .to_string(), + ); + ws.send(msg).await?; + + let msg = ws + .next() + .await + .ok_or_else(|| anyhow!("didn't receive anything"))??; + let payload = msg.to_text()?; + let json_msg: JsonReply = serde_json::from_str(payload)?; + assert_eq!(json_msg.base.janus, "success"); + assert_eq!(json_msg.base.transaction, Some(transaction)); + let session_id = json_msg.data.expect("no session id").id; + + let transaction = transaction_id(); + let msg = WsMessage::Text( + json!({ + "janus": "attach", + "transaction": transaction, + "plugin": "janus.plugin.videoroom", + "session_id": session_id, + }) + .to_string(), + ); + ws.send(msg).await?; + + let msg = ws + .next() + .await + .ok_or_else(|| anyhow!("didn't receive anything"))??; + let payload = msg.to_text()?; + let json_msg: JsonReply = serde_json::from_str(payload)?; + assert_eq!(json_msg.base.janus, "success"); + assert_eq!(json_msg.base.transaction, Some(transaction)); + let handle = json_msg.data.expect("no session id").id; + + let transaction = transaction_id(); + let msg = WsMessage::Text( + json!({ + "janus": "message", + "transaction": transaction, + "session_id": session_id, + "handle_id": handle, + "body": { + "request": "join", + "ptype": "publisher", + "room": args.room_id, + "id": args.feed_id, + }, + }) + .to_string(), + ); + ws.send(msg).await?; + + let webrtcbin = pipeline + .get_by_name("webrtcbin") + .expect("can't find webrtcbin"); + + let webrtc_codec = &args.webrtc_video_codec; + let bin_description = &format!( + "{encoder} name=encoder ! {payloader} ! queue ! capsfilter name=webrtc-vsink caps=\"application/x-rtp,media=video,encoding-name={encoding_name},payload=96\"", + encoder=webrtc_codec.encoder, payloader=webrtc_codec.payloader, + encoding_name=webrtc_codec.encoding_name + ); + + let encode_bin = gst::parse_bin_from_description(bin_description, false)?; + encode_bin.set_name("encode-bin")?; + + pipeline.add(&encode_bin).expect("Failed to add encode bin"); + + let video_queue = pipeline.get_by_name("vqueue").expect("No vqueue found"); + let encoder = encode_bin.get_by_name("encoder").expect("No encoder"); + + let srcpad = video_queue + .get_static_pad("src") + .expect("Failed to get video queue src pad"); + let sinkpad = encoder + .get_static_pad("sink") + .expect("Failed to get sink pad from encoder"); + + if let Ok(video_ghost_pad) = gst::GhostPad::new(Some("video_sink"), &sinkpad) { + encode_bin.add_pad(&video_ghost_pad)?; + srcpad.link(&video_ghost_pad)?; + } + + let sinkpad2 = webrtcbin + .get_request_pad("sink_%u") + .expect("Unable to request outgoing webrtcbin pad"); + let vsink = encode_bin + .get_by_name("webrtc-vsink") + .expect("No webrtc-vsink found"); + let srcpad = vsink + .get_static_pad("src") + .expect("Element without src pad"); + if let Ok(webrtc_ghost_pad) = gst::GhostPad::new(Some("webrtc_video_src"), &srcpad) { + encode_bin.add_pad(&webrtc_ghost_pad)?; + webrtc_ghost_pad.link(&sinkpad2)?; + } + + if let Ok(transceiver) = webrtcbin.emit("get-transceiver", &[&0.to_value()]) { + if let Some(t) = transceiver { + if let Ok(obj) = t.get::() { + obj.expect("Invalid transceiver") + .set_property("do-nack", &true.to_value())?; + } + } + } + + let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); + + let connection_handle = ConnectionHandle { + id: handle, + session_id, + }; + + let peer = Peer(Arc::new(PeerInner { + handle: connection_handle, + bin: pipeline, + webrtcbin, + send_msg_tx: Arc::new(Mutex::new(send_ws_msg_tx)), + })); + + // Connect to on-negotiation-needed to handle sending an Offer + let peer_clone = peer.downgrade(); + peer.webrtcbin + .connect("on-negotiation-needed", false, move |_| { + let peer = upgrade_weak!(peer_clone, None); + if let Err(err) = peer.on_negotiation_needed() { + gst_element_error!( + peer.bin, + gst::LibraryError::Failed, + ("Failed to negotiate: {:?}", err) + ); + } + + None + })?; + + // Whenever there is a new ICE candidate, send it to the peer + let peer_clone = peer.downgrade(); + peer.webrtcbin + .connect("on-ice-candidate", false, move |values| { + let mlineindex = values[1] + .get::() + .expect("Invalid argument") + .expect("Invalid type"); + let candidate = values[2] + .get::() + .expect("Invalid argument") + .expect("Invalid type"); + + let peer = upgrade_weak!(peer_clone, None); + if let Err(err) = peer.on_ice_candidate(mlineindex, candidate) { + gst_element_error!( + peer.bin, + gst::LibraryError::Failed, + ("Failed to send ICE candidate: {:?}", err) + ); + } + + None + })?; + + // Split the websocket into the Sink and Stream + let (ws_sink, ws_stream) = ws.split(); + + Ok(Self { + ws_stream: Some(ws_stream.boxed()), + ws_sink: Some(Box::pin(ws_sink)), + handle: connection_handle, + peer: Mutex::new(peer), + send_ws_msg_rx: Some(send_ws_msg_rx), + }) + } + + pub async fn run(&mut self) -> Result<(), anyhow::Error> { + if let Some(ws_stream) = self.ws_stream.take() { + // Fuse the Stream, required for the select macro + let mut ws_stream = ws_stream.fuse(); + + // Channel for outgoing WebSocket messages from other threads + let send_ws_msg_rx = self + .send_ws_msg_rx + .take() + .expect("Invalid message receiver"); + let mut send_ws_msg_rx = send_ws_msg_rx.fuse(); + + let timer = glib::interval_stream(10_000); + let mut timer_fuse = timer.fuse(); + + let mut sink = self.ws_sink.take().expect("Invalid websocket sink"); + loop { + let ws_msg = futures::select! { + // Handle the WebSocket messages here + ws_msg = ws_stream.select_next_some() => { + match ws_msg? { + WsMessage::Close(_) => { + info!("peer disconnected"); + break + }, + WsMessage::Ping(data) => Some(WsMessage::Pong(data)), + WsMessage::Pong(_) => None, + WsMessage::Binary(_) => None, + WsMessage::Text(text) => { + if let Err(err) = self.handle_websocket_message(&text) { + error!("Failed to parse message: {} ... error: {}", &text, err); + } + None + }, + } + }, + // Handle WebSocket messages we created asynchronously + // to send them out now + ws_msg = send_ws_msg_rx.select_next_some() => Some(ws_msg), + + // Handle keepalive ticks, fired every 10 seconds + ws_msg = timer_fuse.select_next_some() => { + let transaction = transaction_id(); + Some(WsMessage::Text( + json!({ + "janus": "keepalive", + "transaction": transaction, + "handle_id": self.handle.id, + "session_id": self.handle.session_id, + }).to_string(), + )) + }, + // Once we're done, break the loop and return + complete => break, + }; + + // If there's a message to send out, do so now + if let Some(ws_msg) = ws_msg { + sink.send(ws_msg).await?; + } + } + } + Ok(()) + } + + fn handle_jsep(&self, jsep: &JsepHolder) -> Result<(), anyhow::Error> { + if let Some(sdp) = &jsep.sdp { + assert_eq!(jsep.type_, "answer"); + let peer = self.peer.lock().expect("Invalid peer"); + return peer.handle_sdp(&jsep.type_, &sdp); + } else if let Some(ice) = &jsep.ice { + let peer = self.peer.lock().expect("Invalid peer"); + return peer.handle_ice(ice.sdp_mline_index, &ice.candidate); + } + + Ok(()) + } + + // Handle WebSocket messages, both our own as well as WebSocket protocol messages + fn handle_websocket_message(&self, msg: &str) -> Result<(), anyhow::Error> { + trace!("Incoming raw message: {}", msg); + let json_msg: JsonReply = serde_json::from_str(msg)?; + let payload_type = &json_msg.base.janus; + if payload_type == "ack" { + trace!( + "Ack transaction {:#?}, sessionId {:#?}", + json_msg.base.transaction, + json_msg.base.session_id + ); + } else { + debug!("Incoming JSON WebSocket message: {:#?}", json_msg); + } + if payload_type == "event" { + if let Some(_plugin_data) = json_msg.plugin_data { + if let Some(jsep) = json_msg.jsep { + return self.handle_jsep(&jsep); + } + } + } + Ok(()) + } +} diff --git a/webrtc/janus/rust/src/main.rs b/webrtc/janus/rust/src/main.rs new file mode 100644 index 0000000000..eace4d8fcb --- /dev/null +++ b/webrtc/janus/rust/src/main.rs @@ -0,0 +1,186 @@ +// GStreamer +// +// Copyright (C) 2019 Sebastian Dröge +// Copyright (C) 2020 Philippe Normand +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301, USA. + +#![recursion_limit = "256"] + +use anyhow::bail; +use gst::gst_element_error; +use gst::prelude::*; +use std::sync::{Arc, Weak}; + +#[macro_use] +extern crate log; + +mod janus; + +// Strong reference to our application state +#[derive(Debug, Clone)] +struct App(Arc); + +// Weak reference to our application state +#[derive(Debug, Clone)] +struct AppWeak(Weak); + +// Actual application state +#[derive(Debug)] +struct AppInner { + pipeline: gst::Pipeline, +} + +// To be able to access the App's fields directly +impl std::ops::Deref for App { + type Target = AppInner; + + fn deref(&self) -> &AppInner { + &self.0 + } +} + +impl AppWeak { + // Try upgrading a weak reference to a strong one + fn upgrade(&self) -> Option { + self.0.upgrade().map(App) + } +} + +impl App { + // Downgrade the strong reference to a weak reference + fn downgrade(&self) -> AppWeak { + AppWeak(Arc::downgrade(&self.0)) + } + + fn new() -> Result { + let pipeline = gst::parse_launch( + &"webrtcbin name=webrtcbin stun-server=stun://stun.l.google.com:19302 \ + videotestsrc pattern=ball ! videoconvert ! queue name=vqueue" + .to_string(), + )?; + + let pipeline = pipeline + .downcast::() + .expect("Couldn't downcast pipeline"); + + let bus = pipeline.get_bus().unwrap(); + let app = App(Arc::new(AppInner { pipeline })); + + let app_weak = app.downgrade(); + bus.add_watch_local(move |_bus, msg| { + let app = upgrade_weak!(app_weak, glib::Continue(false)); + + if app.handle_pipeline_message(msg).is_err() { + return glib::Continue(false); + } + glib::Continue(true) + }) + .expect("Unable to add bus watch"); + Ok(app) + } + + fn handle_pipeline_message(&self, message: &gst::Message) -> Result<(), anyhow::Error> { + use gst::message::MessageView; + + match message.view() { + MessageView::Error(err) => bail!( + "Error from element {}: {} ({})", + err.get_src() + .map(|s| String::from(s.get_path_string())) + .unwrap_or_else(|| String::from("None")), + err.get_error(), + err.get_debug().unwrap_or_else(|| String::from("None")), + ), + MessageView::Warning(warning) => { + println!("Warning: \"{}\"", warning.get_debug().unwrap()); + } + _ => (), + } + Ok(()) + } + + pub async fn run(&self) -> Result<(), anyhow::Error> { + let bin = self.pipeline.clone().upcast::(); + let mut gw = janus::JanusGateway::new(bin).await?; + + // Asynchronously set the pipeline to Playing + self.pipeline.call_async(|pipeline| { + // If this fails, post an error on the bus so we exit + if pipeline.set_state(gst::State::Playing).is_err() { + gst_element_error!( + pipeline, + gst::LibraryError::Failed, + ("Failed to set pipeline to Playing") + ); + } + }); + + gw.run().await?; + Ok(()) + } +} + +// Make sure to shut down the pipeline when it goes out of scope +// to release any system resources +impl Drop for AppInner { + fn drop(&mut self) { + let _ = self.pipeline.set_state(gst::State::Null); + } +} + +// Check if all GStreamer plugins we require are available +fn check_plugins() -> Result<(), anyhow::Error> { + let needed = [ + "videotestsrc", + "videoconvert", + "autodetect", + "vpx", + "webrtc", + "nice", + "dtls", + "srtp", + "rtpmanager", + "rtp", + ]; + + let registry = gst::Registry::get(); + let missing = needed + .iter() + .filter(|n| registry.find_plugin(n).is_none()) + .cloned() + .collect::>(); + + if !missing.is_empty() { + bail!("Missing plugins: {:?}", missing); + } else { + Ok(()) + } +} + +async fn async_main() -> Result<(), anyhow::Error> { + gst::init()?; + check_plugins()?; + let app = App::new()?; + app.run().await?; + Ok(()) +} + +fn main() -> Result<(), anyhow::Error> { + env_logger::init(); + let main_context = glib::MainContext::default(); + main_context.block_on(async_main()) +} From a8510e63d1459a60ae61ab3829cbc15078f34566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 3 Jul 2020 00:37:47 +0100 Subject: [PATCH 365/412] Release 1.17.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7178b2a5aa..6021d265c0 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.1.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.17.2', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 38d6a5873a663f9354efa3261005abd9c190552b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 3 Jul 2020 02:04:21 +0100 Subject: [PATCH 366/412] Back to development --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6021d265c0..4111b71b50 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.2', license : 'LGPL') +project('gst-examples', 'c', version : '1.17.2.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 61d200a957125a5fc7d6d3536ce8341c1fedd5cd Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Mon, 27 Jul 2020 02:20:59 +0900 Subject: [PATCH 367/412] Port to gst_print* family g_print* would print broken string on Windows See also https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/258 Part-of: --- network/http-launch/http-launch.c | 38 +++--- playback/player/gst-play/gst-play.c | 118 +++++++++--------- playback/player/gtk/gtk-play.c | 10 +- playback/player/qt/extension/qgstplayer.cpp | 4 +- .../gst/mp-webrtc-sendrecv.c | 61 ++++----- webrtc/sendonly/webrtc-recvonly-h264.c | 22 ++-- webrtc/sendonly/webrtc-unidirectional-h264.c | 16 +-- webrtc/sendrecv/gst/webrtc-sendrecv.c | 56 ++++----- 8 files changed, 163 insertions(+), 162 deletions(-) diff --git a/network/http-launch/http-launch.c b/network/http-launch/http-launch.c index 5eedf0062d..ac795483bb 100644 --- a/network/http-launch/http-launch.c +++ b/network/http-launch/http-launch.c @@ -56,7 +56,7 @@ static gboolean caps_resolved = FALSE; static void remove_client (Client * client) { - g_print ("Removing connection %s\n", client->name); + gst_print ("Removing connection %s\n", client->name); G_LOCK (clients); clients = g_list_remove (clients, client); @@ -96,7 +96,7 @@ write_bytes (Client * client, const gchar * data, guint len) if (w <= 0) { if (err) { - g_print ("Write error %s\n", err->message); + gst_print ("Write error %s\n", err->message); g_clear_error (&err); } remove_client (client); @@ -167,15 +167,15 @@ client_message (Client * client, const gchar * data, guint len) g_source_destroy (client->tosource); g_source_unref (client->tosource); client->tosource = NULL; - g_print ("Starting to stream to %s\n", client->name); + gst_print ("Starting to stream to %s\n", client->name); g_signal_emit_by_name (multisocketsink, "add", client->socket); } if (!started) { - g_print ("Starting pipeline\n"); + gst_print ("Starting pipeline\n"); if (gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { - g_print ("Failed to start pipeline\n"); + gst_print ("Failed to start pipeline\n"); g_main_loop_quit (loop); } started = TRUE; @@ -204,7 +204,7 @@ client_message (Client * client, const gchar * data, guint len) static gboolean on_timeout (Client * client) { - g_print ("Timeout\n"); + gst_print ("Timeout\n"); remove_client (client); return FALSE; @@ -250,14 +250,14 @@ on_read_bytes (GPollableInputStream * stream, Client * client) } if (client->current_message->len >= 1024 * 1024) { - g_print ("No complete request after 1MB of data\n"); + gst_print ("No complete request after 1MB of data\n"); remove_client (client); return FALSE; } return TRUE; } else { - g_print ("Read error %s\n", err->message); + gst_print ("Read error %s\n", err->message); g_clear_error (&err); remove_client (client); return FALSE; @@ -284,7 +284,7 @@ on_new_connection (GSocketService * service, GSocketConnection * connection, g_free (ip); g_object_unref (addr); - g_print ("New connection %s\n", client->name); + gst_print ("New connection %s\n", client->name); client->waiting_200_ok = FALSE; client->http_version = g_strdup (""); @@ -324,7 +324,7 @@ on_message (GstBus * bus, GstMessage * message, gpointer user_data) GError *err; gst_message_parse_error (message, &err, &debug); - g_print ("Error %s\n", err->message); + gst_print ("Error %s\n", err->message); g_error_free (err); g_free (debug); g_main_loop_quit (loop); @@ -335,13 +335,13 @@ on_message (GstBus * bus, GstMessage * message, gpointer user_data) GError *err; gst_message_parse_warning (message, &err, &debug); - g_print ("Warning %s\n", err->message); + gst_print ("Warning %s\n", err->message); g_error_free (err); g_free (debug); break; } case GST_MESSAGE_EOS:{ - g_print ("EOS\n"); + gst_print ("EOS\n"); g_main_loop_quit (loop); } default: @@ -404,7 +404,7 @@ on_stream_caps_changed (GObject * obj, GParamSpec * pspec, gpointer user_data) } else { content_type = g_strdup_printf ("Content-Type: %s\r\n", mimetype); } - g_print ("%s", content_type); + gst_print ("%s", content_type); break; } i++; @@ -443,7 +443,7 @@ main (gint argc, gchar ** argv) gst_init (&argc, &argv); if (argc < 4) { - g_print ("usage: %s PORT \n" + gst_print ("usage: %s PORT \n" "example: %s 8080 ( videotestsrc ! theoraenc ! oggmux name=stream )\n", argv[0], argv[0]); return -1; @@ -454,21 +454,21 @@ main (gint argc, gchar ** argv) bin = gst_parse_launchv ((const gchar **) argv + 2, &err); if (!bin) { - g_print ("invalid pipeline: %s\n", err->message); + gst_print ("invalid pipeline: %s\n", err->message); g_clear_error (&err); return -2; } stream = gst_bin_get_by_name (GST_BIN (bin), "stream"); if (!stream) { - g_print ("no element with name \"stream\" found\n"); + gst_print ("no element with name \"stream\" found\n"); gst_object_unref (bin); return -3; } srcpad = gst_element_get_static_pad (stream, "src"); if (!srcpad) { - g_print ("no \"src\" pad in element \"stream\" found\n"); + gst_print ("no \"src\" pad in element \"stream\" found\n"); gst_object_unref (stream); gst_object_unref (bin); return -4; @@ -514,7 +514,7 @@ main (gint argc, gchar ** argv) GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) { gst_object_unref (pipeline); g_main_loop_unref (loop); - g_print ("Failed to set pipeline to ready\n"); + gst_print ("Failed to set pipeline to ready\n"); return -5; } @@ -526,7 +526,7 @@ main (gint argc, gchar ** argv) g_socket_service_start (service); - g_print ("Listening on http://127.0.0.1:%d/\n", port); + gst_print ("Listening on http://127.0.0.1:%d/\n", port); g_main_loop_run (loop); diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index d4c9e67ed2..b7b98e1fd8 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -59,10 +59,10 @@ static void play_set_relative_volume (GstPlay * play, gdouble volume_step); static void end_of_stream_cb (GstPlayer * player, GstPlay * play) { - g_print ("\n"); + gst_print ("\n"); /* and switch to next item in list */ if (!play_next (play)) { - g_print ("Reached end of play list.\n"); + gst_print ("Reached end of play list.\n"); g_main_loop_quit (play->loop); } } @@ -70,14 +70,14 @@ end_of_stream_cb (GstPlayer * player, GstPlay * play) static void error_cb (GstPlayer * player, GError * err, GstPlay * play) { - g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); + gst_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); /* if looping is enabled, then disable it else will keep looping forever */ play->repeat = FALSE; /* try next item in list then */ if (!play_next (play)) { - g_print ("Reached end of play list.\n"); + gst_print ("Reached end of play list.\n"); g_main_loop_quit (play->loop); } } @@ -100,20 +100,20 @@ position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) pstr[9] = '\0'; g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur)); dstr[9] = '\0'; - g_print ("%s / %s %s\r", pstr, dstr, status); + gst_print ("%s / %s %s\r", pstr, dstr, status); } } static void state_changed_cb (GstPlayer * player, GstPlayerState state, GstPlay * play) { - g_print ("State changed: %s\n", gst_player_state_get_name (state)); + gst_print ("State changed: %s\n", gst_player_state_get_name (state)); } static void buffering_cb (GstPlayer * player, gint percent, GstPlay * play) { - g_print ("Buffering: %d\n", percent); + gst_print ("Buffering: %d\n", percent); } static void @@ -127,22 +127,22 @@ print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) val = gst_tag_list_get_value_index (list, tag, i); if (G_VALUE_HOLDS_STRING (val)) { - g_print (" %s : %s \n", tag, g_value_get_string (val)); + gst_print (" %s : %s \n", tag, g_value_get_string (val)); } else if (G_VALUE_HOLDS_UINT (val)) { - g_print (" %s : %u \n", tag, g_value_get_uint (val)); + gst_print (" %s : %u \n", tag, g_value_get_uint (val)); } else if (G_VALUE_HOLDS_DOUBLE (val)) { - g_print (" %s : %g \n", tag, g_value_get_double (val)); + gst_print (" %s : %g \n", tag, g_value_get_double (val)); } else if (G_VALUE_HOLDS_BOOLEAN (val)) { - g_print (" %s : %s \n", tag, + gst_print (" %s : %s \n", tag, g_value_get_boolean (val) ? "true" : "false"); } else if (GST_VALUE_HOLDS_DATE_TIME (val)) { GstDateTime *dt = g_value_get_boxed (val); gchar *dt_str = gst_date_time_to_iso8601_string (dt); - g_print (" %s : %s \n", tag, dt_str); + gst_print (" %s : %s \n", tag, dt_str); g_free (dt_str); } else { - g_print (" %s : tag of type '%s' \n", tag, G_VALUE_TYPE_NAME (val)); + gst_print (" %s : tag of type '%s' \n", tag, G_VALUE_TYPE_NAME (val)); } } } @@ -156,15 +156,15 @@ print_video_info (GstPlayerVideoInfo * info) if (info == NULL) return; - g_print (" width : %d\n", gst_player_video_info_get_width (info)); - g_print (" height : %d\n", gst_player_video_info_get_height (info)); - g_print (" max_bitrate : %d\n", + gst_print (" width : %d\n", gst_player_video_info_get_width (info)); + gst_print (" height : %d\n", gst_player_video_info_get_height (info)); + gst_print (" max_bitrate : %d\n", gst_player_video_info_get_max_bitrate (info)); - g_print (" bitrate : %d\n", gst_player_video_info_get_bitrate (info)); + gst_print (" bitrate : %d\n", gst_player_video_info_get_bitrate (info)); gst_player_video_info_get_framerate (info, &fps_n, &fps_d); - g_print (" framerate : %.2f\n", (gdouble) fps_n / fps_d); + gst_print (" framerate : %.2f\n", (gdouble) fps_n / fps_d); gst_player_video_info_get_pixel_aspect_ratio (info, &par_n, &par_d); - g_print (" pixel-aspect-ratio %u:%u\n", par_n, par_d); + gst_print (" pixel-aspect-ratio %u:%u\n", par_n, par_d); } static void @@ -173,13 +173,13 @@ print_audio_info (GstPlayerAudioInfo * info) if (info == NULL) return; - g_print (" sample rate : %d\n", + gst_print (" sample rate : %d\n", gst_player_audio_info_get_sample_rate (info)); - g_print (" channels : %d\n", gst_player_audio_info_get_channels (info)); - g_print (" max_bitrate : %d\n", + gst_print (" channels : %d\n", gst_player_audio_info_get_channels (info)); + gst_print (" max_bitrate : %d\n", gst_player_audio_info_get_max_bitrate (info)); - g_print (" bitrate : %d\n", gst_player_audio_info_get_bitrate (info)); - g_print (" language : %s\n", gst_player_audio_info_get_language (info)); + gst_print (" bitrate : %d\n", gst_player_audio_info_get_bitrate (info)); + gst_print (" language : %s\n", gst_player_audio_info_get_language (info)); } static void @@ -188,7 +188,7 @@ print_subtitle_info (GstPlayerSubtitleInfo * info) if (info == NULL) return; - g_print (" language : %s\n", gst_player_subtitle_info_get_language (info)); + gst_print (" language : %s\n", gst_player_subtitle_info_get_language (info)); } static void @@ -197,31 +197,31 @@ print_all_stream_info (GstPlayerMediaInfo * media_info) guint count = 0; GList *list, *l; - g_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); - g_print ("Duration: %" GST_TIME_FORMAT "\n", + gst_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); + gst_print ("Duration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); - g_print ("Global taglist:\n"); + gst_print ("Global taglist:\n"); if (gst_player_media_info_get_tags (media_info)) gst_tag_list_foreach (gst_player_media_info_get_tags (media_info), print_one_tag, NULL); else - g_print (" (nil) \n"); + gst_print (" (nil) \n"); list = gst_player_media_info_get_stream_list (media_info); if (!list) return; - g_print ("All Stream information\n"); + gst_print ("All Stream information\n"); for (l = list; l != NULL; l = l->next) { GstTagList *tags = NULL; GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; - g_print (" Stream # %u \n", count++); - g_print (" type : %s_%u\n", + gst_print (" Stream # %u \n", count++); + gst_print (" type : %s_%u\n", gst_player_stream_info_get_stream_type (stream), gst_player_stream_info_get_index (stream)); tags = gst_player_stream_info_get_tags (stream); - g_print (" taglist : \n"); + gst_print (" taglist : \n"); if (tags) { gst_tag_list_foreach (tags, print_one_tag, NULL); } @@ -244,11 +244,11 @@ print_all_video_stream (GstPlayerMediaInfo * media_info) if (!list) return; - g_print ("All video streams\n"); + gst_print ("All video streams\n"); for (l = list; l != NULL; l = l->next) { GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), gst_player_stream_info_get_index (sinfo)); print_video_info (info); } @@ -263,11 +263,11 @@ print_all_subtitle_stream (GstPlayerMediaInfo * media_info) if (!list) return; - g_print ("All subtitle streams:\n"); + gst_print ("All subtitle streams:\n"); for (l = list; l != NULL; l = l->next) { GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), gst_player_stream_info_get_index (sinfo)); print_subtitle_info (info); } @@ -282,11 +282,11 @@ print_all_audio_stream (GstPlayerMediaInfo * media_info) if (!list) return; - g_print ("All audio streams: \n"); + gst_print ("All audio streams: \n"); for (l = list; l != NULL; l = l->next) { GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) l->data; GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - g_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), + gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), gst_player_stream_info_get_index (sinfo)); print_audio_info (info); } @@ -299,15 +299,15 @@ print_current_tracks (GstPlay * play) GstPlayerVideoInfo *video = NULL; GstPlayerSubtitleInfo *subtitle = NULL; - g_print ("Current video track: \n"); + gst_print ("Current video track: \n"); video = gst_player_get_current_video_track (play->player); print_video_info (video); - g_print ("Current audio track: \n"); + gst_print ("Current audio track: \n"); audio = gst_player_get_current_audio_track (play->player); print_audio_info (audio); - g_print ("Current subtitle track: \n"); + gst_print ("Current subtitle track: \n"); subtitle = gst_player_get_current_subtitle_track (play->player); print_subtitle_info (subtitle); @@ -325,11 +325,11 @@ static void print_media_info (GstPlayerMediaInfo * media_info) { print_all_stream_info (media_info); - g_print ("\n"); + gst_print ("\n"); print_all_video_stream (media_info); - g_print ("\n"); + gst_print ("\n"); print_all_audio_stream (media_info); - g_print ("\n"); + gst_print ("\n"); print_all_subtitle_stream (media_info); } @@ -410,7 +410,7 @@ play_set_relative_volume (GstPlay * play, gdouble volume_step) g_object_set (play->player, "volume", volume, NULL); - g_print ("Volume: %.0f%% \n", volume * 100); + gst_print ("Volume: %.0f%% \n", volume * 100); } static gchar * @@ -438,7 +438,7 @@ play_uri (GstPlay * play, const gchar * next_uri) play_reset (play); loc = play_uri_get_display_name (play, next_uri); - g_print ("Now playing %s\n", loc); + gst_print ("Now playing %s\n", loc); g_free (loc); g_object_set (play->player, "uri", next_uri, NULL); @@ -451,7 +451,7 @@ play_next (GstPlay * play) { if ((play->cur_idx + 1) >= play->num_uris) { if (play->repeat) { - g_print ("Looping playlist \n"); + gst_print ("Looping playlist \n"); play->cur_idx = -1; } else return FALSE; @@ -567,7 +567,7 @@ relative_seek (GstPlay * play, gdouble percent) g_object_get (play->player, "position", &pos, "duration", &dur, NULL); if (dur <= 0) { - g_print ("\nCould not seek.\n"); + gst_print ("\nCould not seek.\n"); return; } @@ -602,7 +602,7 @@ keyboard_cb (const gchar * key_input, gpointer user_data) break; case '>': if (!play_next (play)) { - g_print ("\nReached end of play list.\n"); + gst_print ("\nReached end of play list.\n"); g_main_loop_quit (play->loop); } break; @@ -671,7 +671,7 @@ main (int argc, char **argv) g_option_context_add_main_entries (ctx, options, NULL); g_option_context_add_group (ctx, gst_init_get_option_group ()); if (!g_option_context_parse (ctx, &argc, &argv, &err)) { - g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + gst_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); g_clear_error (&err); g_option_context_free (ctx); return 1; @@ -684,8 +684,8 @@ main (int argc, char **argv) gchar *version_str; version_str = gst_version_string (); - g_print ("%s version %s\n", g_get_prgname (), "1.0"); - g_print ("%s\n", version_str); + gst_print ("%s version %s\n", g_get_prgname (), "1.0"); + gst_print ("%s\n", version_str); g_free (version_str); g_free (playlist_file); @@ -712,7 +712,7 @@ main (int argc, char **argv) g_strfreev (lines); g_free (playlist_contents); } else { - g_printerr ("Could not read playlist: %s\n", err->message); + gst_printerr ("Could not read playlist: %s\n", err->message); g_clear_error (&err); } g_free (playlist_file); @@ -720,10 +720,10 @@ main (int argc, char **argv) } if (playlist->len == 0 && (filenames == NULL || *filenames == NULL)) { - g_printerr ("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...", + gst_printerr ("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...", "gst-play"); - g_printerr ("\n\n"), - g_printerr ("%s\n\n", + gst_printerr ("\n\n"), + gst_printerr ("%s\n\n", "You must provide at least one filename or URI to play."); /* No input provided. Free array */ g_ptr_array_free (playlist, TRUE); @@ -757,7 +757,7 @@ main (int argc, char **argv) if (gst_play_kb_set_key_handler (keyboard_cb, play)) { atexit (restore_terminal); } else { - g_print ("Interactive keyboard handling in terminal not available.\n"); + gst_print ("Interactive keyboard handling in terminal not available.\n"); } } @@ -767,7 +767,7 @@ main (int argc, char **argv) /* clean up */ play_free (play); - g_print ("\n"); + gst_print ("\n"); gst_deinit (); return 0; } diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 3cea7e6cfe..786458e298 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -162,7 +162,7 @@ load_from_builder (const gchar * filename, gboolean register_sig_handler, builder = gtk_builder_new_from_resource (filename); if (builder == NULL) { - g_print ("ERROR: failed to load %s \n", filename); + gst_print ("ERROR: failed to load %s \n", filename); return NULL; } @@ -1358,7 +1358,7 @@ gtk_widget_apply_css (GtkWidget * widget, const gchar * filename) provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); bytes = g_resources_lookup_data (filename, 0, &err); if (err) { - g_print ("ERROR: failed to apply css %s '%s' \n", filename, err->message); + gst_print ("ERROR: failed to apply css %s '%s' \n", filename, err->message); return; } data = g_bytes_get_data (bytes, &data_size); @@ -1612,12 +1612,12 @@ gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) && (type != GST_TAG_IMAGE_TYPE_UNDEFINED) && (type != GST_TAG_IMAGE_TYPE_NONE)) { - g_print ("unsupport type ... %d \n", type); + gst_print ("unsupport type ... %d \n", type); return NULL; } if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { - g_print ("failed to map gst buffer \n"); + gst_print ("failed to map gst buffer \n"); return NULL; } @@ -1628,7 +1628,7 @@ gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) if (pixbuf) { g_object_ref (pixbuf); } else { - g_print ("failed to convert gst buffer to pixbuf %s \n", err->message); + gst_print ("failed to convert gst buffer to pixbuf %s \n", err->message); g_error_free (err); } } diff --git a/playback/player/qt/extension/qgstplayer.cpp b/playback/player/qt/extension/qgstplayer.cpp index 6667e8d6c4..4ea16aab96 100644 --- a/playback/player/qt/extension/qgstplayer.cpp +++ b/playback/player/qt/extension/qgstplayer.cpp @@ -184,12 +184,12 @@ void MediaInfo::update(GstPlayerMediaInfo *info) if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) && (type != GST_TAG_IMAGE_TYPE_UNDEFINED) && (type != GST_TAG_IMAGE_TYPE_NONE)) { - g_print ("unsupport type ... %d \n", type); + gst_print ("unsupport type ... %d \n", type); return; } if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ)) { - g_print ("failed to map gst buffer \n"); + gst_print ("failed to map gst buffer \n"); return; } diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 1c5b0ecc23..5f8076eeb5 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -78,7 +78,7 @@ static gboolean cleanup_and_quit_loop (const gchar * msg, enum AppState state) { if (msg) - g_printerr ("%s\n", msg); + gst_printerr ("%s\n", msg); if (state > 0) app_state = state; @@ -153,7 +153,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, const gchar *name; if (!gst_pad_has_current_caps (pad)) { - g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + gst_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", GST_PAD_NAME (pad)); return; } @@ -166,7 +166,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, } else if (g_str_has_prefix (name, "audio")) { handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); } else { - g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + gst_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); } } @@ -240,7 +240,7 @@ send_room_peer_sdp (GstWebRTCSessionDescription * desc, const gchar * peer_id) g_assert_not_reached (); text = gst_sdp_message_as_text (desc->sdp); - g_print ("Sending sdp %s to %s:\n%s\n", sdptype, peer_id, text); + gst_print ("Sending sdp %s to %s:\n%s\n", sdptype, peer_id, text); sdp = json_object_new (); json_object_set_string_member (sdp, "type", sdptype); @@ -420,12 +420,12 @@ start_pipeline (void) "queue ! " RTP_CAPS_OPUS (96) " ! audiotee. ", &error); if (error) { - g_printerr ("Failed to parse launch: %s\n", error->message); + gst_printerr ("Failed to parse launch: %s\n", error->message); g_error_free (error); goto err; } - g_print ("Starting pipeline, not transmitting yet\n"); + gst_print ("Starting pipeline, not transmitting yet\n"); ret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) goto err; @@ -433,7 +433,7 @@ start_pipeline (void) return TRUE; err: - g_print ("State change failure\n"); + gst_print ("State change failure\n"); if (pipeline) g_clear_object (&pipeline); return FALSE; @@ -451,7 +451,7 @@ join_room_on_server (void) if (!room_id) return FALSE; - g_print ("Joining room %s\n", room_id); + gst_print ("Joining room %s\n", room_id); app_state = ROOM_JOINING; msg = g_strdup_printf ("ROOM %s", room_id); soup_websocket_connection_send_text (ws_conn, msg); @@ -468,7 +468,7 @@ register_with_server (void) SOUP_WEBSOCKET_STATE_OPEN) return FALSE; - g_print ("Registering id %s with server\n", local_id); + gst_print ("Registering id %s with server\n", local_id); app_state = SERVER_REGISTERING; /* Register with the server with a random integer id. Reply will be received @@ -497,7 +497,7 @@ do_registration (void) return FALSE; } app_state = SERVER_REGISTERED; - g_print ("Registered with server\n"); + gst_print ("Registered with server\n"); /* Ask signalling server that we want to join a room */ if (!join_room_on_server ()) { cleanup_and_quit_loop ("ERROR: Failed to join room", ROOM_CALL_ERROR); @@ -523,7 +523,7 @@ do_join_room (const gchar * text) } app_state = ROOM_JOINED; - g_print ("Room joined\n"); + gst_print ("Room joined\n"); /* Start recording, but not transmitting */ if (!start_pipeline ()) { cleanup_and_quit_loop ("ERROR: Failed to start pipeline", ROOM_CALL_ERROR); @@ -536,11 +536,11 @@ do_join_room (const gchar * text) /* There are peers in the room already. We need to start negotiation * (exchange SDP and ICE candidates) and transmission of media. */ if (len > 1 && strlen (peer_ids[1]) > 0) { - g_print ("Found %i peers already in room\n", len - 1); + gst_print ("Found %i peers already in room\n", len - 1); app_state = ROOM_CALL_OFFERING; for (ii = 1; ii < len; ii++) { gchar *peer_id = g_strdup (peer_ids[ii]); - g_print ("Negotiating with peer %s\n", peer_id); + gst_print ("Negotiating with peer %s\n", peer_id); /* This might fail asynchronously */ call_peer (peer_id); peers = g_list_prepend (peers, peer_id); @@ -621,7 +621,7 @@ handle_sdp_offer (const gchar * peer_id, const gchar * text) g_assert_cmpint (app_state, ==, ROOM_CALL_ANSWERING); - g_print ("Received offer:\n%s\n", text); + gst_print ("Received offer:\n%s\n", text); ret = gst_sdp_message_new (&sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); @@ -661,7 +661,7 @@ handle_sdp_answer (const gchar * peer_id, const gchar * text) g_assert_cmpint (app_state, >=, ROOM_CALL_OFFERING); - g_print ("Received answer:\n%s\n", text); + gst_print ("Received answer:\n%s\n", text); ret = gst_sdp_message_new (&sdp); g_assert_cmpint (ret, ==, GST_SDP_OK); @@ -690,19 +690,20 @@ handle_peer_message (const gchar * peer_id, const gchar * msg) JsonObject *object, *child; JsonParser *parser = json_parser_new (); if (!json_parser_load_from_data (parser, msg, -1, NULL)) { - g_printerr ("Unknown message '%s' from '%s', ignoring", msg, peer_id); + gst_printerr ("Unknown message '%s' from '%s', ignoring", msg, peer_id); g_object_unref (parser); return FALSE; } root = json_parser_get_root (parser); if (!JSON_NODE_HOLDS_OBJECT (root)) { - g_printerr ("Unknown json message '%s' from '%s', ignoring", msg, peer_id); + gst_printerr ("Unknown json message '%s' from '%s', ignoring", msg, + peer_id); g_object_unref (parser); return FALSE; } - g_print ("Message from peer %s: %s\n", peer_id, msg); + gst_print ("Message from peer %s: %s\n", peer_id, msg); object = json_node_get_object (root); /* Check type of JSON message */ @@ -750,7 +751,7 @@ handle_peer_message (const gchar * peer_id, const gchar * msg) candidate); gst_object_unref (webrtc); } else { - g_printerr ("Ignoring unknown JSON message:\n%s\n", msg); + gst_printerr ("Ignoring unknown JSON message:\n%s\n", msg); } g_object_unref (parser); return TRUE; @@ -765,7 +766,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, switch (type) { case SOUP_WEBSOCKET_DATA_BINARY: - g_printerr ("Received unknown binary message, ignoring\n"); + gst_printerr ("Received unknown binary message, ignoring\n"); return; case SOUP_WEBSOCKET_DATA_TEXT:{ gsize size; @@ -803,18 +804,18 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, peers = g_list_prepend (peers, g_strdup (splitm[1])); peer_id = find_peer_from_list (splitm[1]); g_assert_nonnull (peer_id); - g_print ("Peer %s has joined the room\n", peer_id); + gst_print ("Peer %s has joined the room\n", peer_id); } else if (g_str_has_prefix (text, "ROOM_PEER_LEFT")) { splitm = g_strsplit (text, " ", 2); peer_id = find_peer_from_list (splitm[1]); g_assert_nonnull (peer_id); peers = g_list_remove (peers, peer_id); - g_print ("Peer %s has left the room\n", peer_id); + gst_print ("Peer %s has left the room\n", peer_id); remove_peer_from_pipeline (peer_id); g_free ((gchar *) peer_id); /* TODO: cleanup pipeline */ } else { - g_printerr ("WARNING: Ignoring unknown message %s\n", text); + gst_printerr ("WARNING: Ignoring unknown message %s\n", text); } g_strfreev (splitm); } else { @@ -856,7 +857,7 @@ on_server_connected (SoupSession * session, GAsyncResult * res, g_assert_nonnull (ws_conn); app_state = SERVER_CONNECTED; - g_print ("Connected to signalling server\n"); + gst_print ("Connected to signalling server\n"); g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL); g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL); @@ -888,7 +889,7 @@ connect_to_websocket_server_async (void) message = soup_message_new (SOUP_METHOD_GET, server_url); - g_print ("Connecting to server...\n"); + gst_print ("Connecting to server...\n"); /* Once connected, we will register */ soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, @@ -912,7 +913,7 @@ check_plugins (void) GstPlugin *plugin; plugin = gst_registry_find_plugin (registry, needed[i]); if (!plugin) { - g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); + gst_print ("Required gstreamer plugin '%s' not found\n", needed[i]); ret = FALSE; continue; } @@ -931,7 +932,7 @@ main (int argc, char *argv[]) g_option_context_add_main_entries (context, entries, NULL); g_option_context_add_group (context, gst_init_get_option_group ()); if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_printerr ("Error initializing: %s\n", error->message); + gst_printerr ("Error initializing: %s\n", error->message); return -1; } @@ -939,7 +940,7 @@ main (int argc, char *argv[]) return -1; if (!room_id) { - g_printerr ("--room-id is a required argument\n"); + gst_printerr ("--room-id is a required argument\n"); return -1; } @@ -949,7 +950,7 @@ main (int argc, char *argv[]) /* Sanitize by removing whitespace, modifies string in-place */ g_strdelimit (local_id, " \t\n\r", '-'); - g_print ("Our local id is %s\n", local_id); + gst_print ("Our local id is %s\n", local_id); if (!server_url) server_url = g_strdup (default_server_url); @@ -971,7 +972,7 @@ main (int argc, char *argv[]) g_main_loop_run (loop); gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); - g_print ("Pipeline stopped\n"); + gst_print ("Pipeline stopped\n"); gst_object_unref (pipeline); g_free (server_url); diff --git a/webrtc/sendonly/webrtc-recvonly-h264.c b/webrtc/sendonly/webrtc-recvonly-h264.c index 5a5508afc2..1fc9a1dfa2 100644 --- a/webrtc/sendonly/webrtc-recvonly-h264.c +++ b/webrtc/sendonly/webrtc-recvonly-h264.c @@ -190,7 +190,7 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, GstElement *q, *conv, *resample, *sink; GstPadLinkReturn ret; - g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); + gst_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); q = gst_element_factory_make ("queue", NULL); g_assert_nonnull (q); @@ -232,7 +232,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, const gchar *name; if (!gst_pad_has_current_caps (pad)) { - g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + gst_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", GST_PAD_NAME (pad)); return; } @@ -245,7 +245,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, } else if (g_str_has_prefix (name, "audio")) { handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); } else { - g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + gst_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); } } @@ -386,7 +386,7 @@ on_offer_created_cb (GstPromise * promise, gpointer user_data) gst_promise_unref (local_desc_promise); sdp_string = gst_sdp_message_as_text (offer->sdp); - g_print ("Negotiation offer created:\n%s\n", sdp_string); + gst_print ("Negotiation offer created:\n%s\n", sdp_string); sdp_json = json_object_new (); json_object_set_string_member (sdp_json, "type", "sdp"); @@ -413,7 +413,7 @@ on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data) GstPromise *promise; ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; - g_print ("Creating negotiation offer\n"); + gst_print ("Creating negotiation offer\n"); promise = gst_promise_new_with_change_func (on_offer_created_cb, (gpointer) receiver_entry, NULL); @@ -525,7 +525,7 @@ soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, } sdp_string = json_object_get_string_member (data_json_object, "sdp"); - g_print ("Received SDP:\n%s\n", sdp_string); + gst_print ("Received SDP:\n%s\n", sdp_string); ret = gst_sdp_message_new (&sdp); g_assert_cmphex (ret, ==, GST_SDP_OK); @@ -566,7 +566,7 @@ soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, candidate_string = json_object_get_string_member (data_json_object, "candidate"); - g_print ("Received ICE candidate with mline index %u; candidate: %s\n", + gst_print ("Received ICE candidate with mline index %u; candidate: %s\n", mline_index, candidate_string); g_signal_emit_by_name (receiver_entry->webrtcbin, "add-ice-candidate", @@ -592,7 +592,7 @@ soup_websocket_closed_cb (SoupWebsocketConnection * connection, { GHashTable *receiver_entry_table = (GHashTable *) user_data; g_hash_table_remove (receiver_entry_table, connection); - g_print ("Closed websocket connection %p\n", (gpointer) connection); + gst_print ("Closed websocket connection %p\n", (gpointer) connection); } @@ -629,7 +629,7 @@ soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, ReceiverEntry *receiver_entry; GHashTable *receiver_entry_table = (GHashTable *) user_data; - g_print ("Processing new websocket connection %p", (gpointer) connection); + gst_print ("Processing new websocket connection %p", (gpointer) connection); g_signal_connect (G_OBJECT (connection), "closed", G_CALLBACK (soup_websocket_closed_cb), (gpointer) receiver_entry_table); @@ -662,7 +662,7 @@ get_string_from_json_object (JsonObject * object) gboolean exit_sighandler (gpointer user_data) { - g_print ("Caught signal, stopping mainloop\n"); + gst_print ("Caught signal, stopping mainloop\n"); GMainLoop *mainloop = (GMainLoop *) user_data; g_main_loop_quit (mainloop); return TRUE; @@ -699,7 +699,7 @@ main (int argc, char *argv[]) soup_server_listen_all (soup_server, SOUP_HTTP_PORT, (SoupServerListenOptions) 0, NULL); - g_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); + gst_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); g_main_loop_run (mainloop); diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index b8060145fe..e297a534d5 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -260,7 +260,7 @@ on_offer_created_cb (GstPromise * promise, gpointer user_data) gst_promise_unref (local_desc_promise); sdp_string = gst_sdp_message_as_text (offer->sdp); - g_print ("Negotiation offer created:\n%s\n", sdp_string); + gst_print ("Negotiation offer created:\n%s\n", sdp_string); sdp_json = json_object_new (); json_object_set_string_member (sdp_json, "type", "sdp"); @@ -287,7 +287,7 @@ on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data) GstPromise *promise; ReceiverEntry *receiver_entry = (ReceiverEntry *) user_data; - g_print ("Creating negotiation offer\n"); + gst_print ("Creating negotiation offer\n"); promise = gst_promise_new_with_change_func (on_offer_created_cb, (gpointer) receiver_entry, NULL); @@ -399,7 +399,7 @@ soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, } sdp_string = json_object_get_string_member (data_json_object, "sdp"); - g_print ("Received SDP:\n%s\n", sdp_string); + gst_print ("Received SDP:\n%s\n", sdp_string); ret = gst_sdp_message_new (&sdp); g_assert_cmphex (ret, ==, GST_SDP_OK); @@ -440,7 +440,7 @@ soup_websocket_message_cb (G_GNUC_UNUSED SoupWebsocketConnection * connection, candidate_string = json_object_get_string_member (data_json_object, "candidate"); - g_print ("Received ICE candidate with mline index %u; candidate: %s\n", + gst_print ("Received ICE candidate with mline index %u; candidate: %s\n", mline_index, candidate_string); g_signal_emit_by_name (receiver_entry->webrtcbin, "add-ice-candidate", @@ -466,7 +466,7 @@ soup_websocket_closed_cb (SoupWebsocketConnection * connection, { GHashTable *receiver_entry_table = (GHashTable *) user_data; g_hash_table_remove (receiver_entry_table, connection); - g_print ("Closed websocket connection %p\n", (gpointer) connection); + gst_print ("Closed websocket connection %p\n", (gpointer) connection); } @@ -503,7 +503,7 @@ soup_websocket_handler (G_GNUC_UNUSED SoupServer * server, ReceiverEntry *receiver_entry; GHashTable *receiver_entry_table = (GHashTable *) user_data; - g_print ("Processing new websocket connection %p", (gpointer) connection); + gst_print ("Processing new websocket connection %p", (gpointer) connection); g_signal_connect (G_OBJECT (connection), "closed", G_CALLBACK (soup_websocket_closed_cb), (gpointer) receiver_entry_table); @@ -536,7 +536,7 @@ get_string_from_json_object (JsonObject * object) gboolean exit_sighandler (gpointer user_data) { - g_print ("Caught signal, stopping mainloop\n"); + gst_print ("Caught signal, stopping mainloop\n"); GMainLoop *mainloop = (GMainLoop *) user_data; g_main_loop_quit (mainloop); return TRUE; @@ -573,7 +573,7 @@ main (int argc, char *argv[]) soup_server_listen_all (soup_server, SOUP_HTTP_PORT, (SoupServerListenOptions) 0, NULL); - g_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); + gst_print ("WebRTC page link: http://127.0.0.1:%d/\n", (gint) SOUP_HTTP_PORT); g_main_loop_run (mainloop); diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 4453c954da..8c4c9f0be5 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -65,7 +65,7 @@ static gboolean cleanup_and_quit_loop (const gchar * msg, enum AppState state) { if (msg) - g_printerr ("%s\n", msg); + gst_printerr ("%s\n", msg); if (state > 0) app_state = state; @@ -114,7 +114,7 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, GstElement *q, *conv, *resample, *sink; GstPadLinkReturn ret; - g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); + gst_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); q = gst_element_factory_make ("queue", NULL); g_assert_nonnull (q); @@ -156,7 +156,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, const gchar *name; if (!gst_pad_has_current_caps (pad)) { - g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", + gst_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", GST_PAD_NAME (pad)); return; } @@ -169,7 +169,7 @@ on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, } else if (g_str_has_prefix (name, "audio")) { handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); } else { - g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); + gst_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); } } @@ -233,10 +233,10 @@ send_sdp_to_peer (GstWebRTCSessionDescription * desc) sdp = json_object_new (); if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) { - g_print ("Sending offer:\n%s\n", text); + gst_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); + gst_print ("Sending answer:\n%s\n", text); json_object_set_string_member (sdp, "type", "answer"); } else { g_assert_not_reached (); @@ -310,7 +310,7 @@ static void data_channel_on_open (GObject * dc, gpointer user_data) { GBytes *bytes = g_bytes_new ("data", strlen ("data")); - g_print ("data channel opened\n"); + gst_print ("data channel opened\n"); g_signal_emit_by_name (dc, "send-string", "Hi! from GStreamer"); g_signal_emit_by_name (dc, "send-data", bytes); g_bytes_unref (bytes); @@ -325,7 +325,7 @@ data_channel_on_close (GObject * dc, gpointer user_data) static void data_channel_on_message_string (GObject * dc, gchar * str, gpointer user_data) { - g_print ("Received data channel message: %s\n", str); + gst_print ("Received data channel message: %s\n", str); } static void @@ -368,7 +368,7 @@ on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec, new_state = "complete"; break; } - g_print ("ICE gathering state changed to %s\n", new_state); + gst_print ("ICE gathering state changed to %s\n", new_state); } static gboolean @@ -386,7 +386,7 @@ start_pipeline (void) "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", &error); if (error) { - g_printerr ("Failed to parse launch: %s\n", error->message); + gst_printerr ("Failed to parse launch: %s\n", error->message); g_error_free (error); goto err; } @@ -411,10 +411,10 @@ start_pipeline (void) g_signal_emit_by_name (webrtc1, "create-data-channel", "channel", NULL, &send_channel); if (send_channel) { - g_print ("Created data channel\n"); + gst_print ("Created data channel\n"); connect_data_channel_signals (send_channel); } else { - g_print ("Could not create data channel, is usrsctp available?\n"); + gst_print ("Could not create data channel, is usrsctp available?\n"); } g_signal_connect (webrtc1, "on-data-channel", G_CALLBACK (on_data_channel), @@ -425,7 +425,7 @@ start_pipeline (void) /* Lifetime is the same as the pipeline itself */ gst_object_unref (webrtc1); - g_print ("Starting pipeline\n"); + gst_print ("Starting pipeline\n"); ret = gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) goto err; @@ -452,7 +452,7 @@ setup_call (void) if (!peer_id) return FALSE; - g_print ("Setting up signalling server call with %s\n", peer_id); + gst_print ("Setting up signalling server call with %s\n", peer_id); app_state = PEER_CONNECTING; msg = g_strdup_printf ("SESSION %s", peer_id); soup_websocket_connection_send_text (ws_conn, msg); @@ -471,7 +471,7 @@ register_with_server (void) return FALSE; our_id = g_random_int_range (10, 10000); - g_print ("Registering id %i with server\n", our_id); + gst_print ("Registering id %i with server\n", our_id); app_state = SERVER_REGISTERING; /* Register with the server with a random integer id. Reply will be received @@ -550,7 +550,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, switch (type) { case SOUP_WEBSOCKET_DATA_BINARY: - g_printerr ("Received unknown binary message, ignoring\n"); + gst_printerr ("Received unknown binary message, ignoring\n"); return; case SOUP_WEBSOCKET_DATA_TEXT:{ gsize size; @@ -571,7 +571,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, goto out; } app_state = SERVER_REGISTERED; - g_print ("Registered with server\n"); + gst_print ("Registered with server\n"); /* Ask signalling server to connect us with a specific peer */ if (!setup_call ()) { cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); @@ -616,14 +616,14 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, JsonObject *object, *child; JsonParser *parser = json_parser_new (); if (!json_parser_load_from_data (parser, text, -1, NULL)) { - g_printerr ("Unknown message '%s', ignoring", text); + gst_printerr ("Unknown message '%s', ignoring", text); g_object_unref (parser); goto out; } root = json_parser_get_root (parser); if (!JSON_NODE_HOLDS_OBJECT (root)) { - g_printerr ("Unknown json message '%s', ignoring", text); + gst_printerr ("Unknown json message '%s', ignoring", text); g_object_unref (parser); goto out; } @@ -660,7 +660,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_assert_cmphex (ret, ==, GST_SDP_OK); if (g_str_equal (sdptype, "answer")) { - g_print ("Received answer:\n%s\n", text); + gst_print ("Received answer:\n%s\n", text); answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, sdp); g_assert_nonnull (answer); @@ -675,7 +675,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, } app_state = PEER_CALL_STARTED; } else { - g_print ("Received offer:\n%s\n", text); + gst_print ("Received offer:\n%s\n", text); on_offer_received (sdp); } @@ -691,7 +691,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_signal_emit_by_name (webrtc1, "add-ice-candidate", sdpmlineindex, candidate); } else { - g_printerr ("Ignoring unknown JSON message:\n%s\n", text); + gst_printerr ("Ignoring unknown JSON message:\n%s\n", text); } g_object_unref (parser); } @@ -716,7 +716,7 @@ on_server_connected (SoupSession * session, GAsyncResult * res, g_assert_nonnull (ws_conn); app_state = SERVER_CONNECTED; - g_print ("Connected to signalling server\n"); + gst_print ("Connected to signalling server\n"); g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL); g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL); @@ -748,7 +748,7 @@ connect_to_websocket_server_async (void) message = soup_message_new (SOUP_METHOD_GET, server_url); - g_print ("Connecting to server...\n"); + gst_print ("Connecting to server...\n"); /* Once connected, we will register */ soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, @@ -772,7 +772,7 @@ check_plugins (void) for (i = 0; i < g_strv_length ((gchar **) needed); i++) { plugin = gst_registry_find_plugin (registry, needed[i]); if (!plugin) { - g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); + gst_print ("Required gstreamer plugin '%s' not found\n", needed[i]); ret = FALSE; continue; } @@ -791,7 +791,7 @@ main (int argc, char *argv[]) g_option_context_add_main_entries (context, entries, NULL); g_option_context_add_group (context, gst_init_get_option_group ()); if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_printerr ("Error initializing: %s\n", error->message); + gst_printerr ("Error initializing: %s\n", error->message); return -1; } @@ -799,7 +799,7 @@ main (int argc, char *argv[]) return -1; if (!peer_id) { - g_printerr ("--peer-id is a required argument\n"); + gst_printerr ("--peer-id is a required argument\n"); return -1; } @@ -822,7 +822,7 @@ main (int argc, char *argv[]) if (pipe1) { gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); - g_print ("Pipeline stopped\n"); + gst_print ("Pipeline stopped\n"); gst_object_unref (pipe1); } From 3492c81fcf43741b20d30b85016c9411042e2219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 31 Jul 2020 11:51:43 +0300 Subject: [PATCH 368/412] Update Rust examples to latest bindings versions Part-of: --- webrtc/janus/rust/Cargo.lock | 306 +++-- webrtc/janus/rust/Cargo.toml | 12 +- webrtc/janus/rust/src/janus.rs | 21 +- .../multiparty-sendrecv/gst-rust/Cargo.lock | 1015 ++++++++++------- .../multiparty-sendrecv/gst-rust/Cargo.toml | 8 +- .../multiparty-sendrecv/gst-rust/src/main.rs | 38 +- webrtc/sendrecv/gst-rust/Cargo.lock | 737 +++++++----- webrtc/sendrecv/gst-rust/Cargo.toml | 8 +- webrtc/sendrecv/gst-rust/src/main.rs | 24 +- 9 files changed, 1259 insertions(+), 910 deletions(-) diff --git a/webrtc/janus/rust/Cargo.lock b/webrtc/janus/rust/Cargo.lock index 3cde9a5a99..0ae8b18a83 100644 --- a/webrtc/janus/rust/Cargo.lock +++ b/webrtc/janus/rust/Cargo.lock @@ -11,15 +11,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" [[package]] name = "async-tungstenite" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c35882200d230428ae9cf74acb72aaa9c0a912911b4dad8fb890da49608e127" +checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" dependencies = [ "futures-io", "futures-util", @@ -94,18 +94,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" -dependencies = [ - "loom", -] - -[[package]] -name = "cc" -version = "1.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1be3409f94d7bdceeb5f5fac551039d9b3f00e25da7a74fc4d33400a0d96368" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "cfg-if" @@ -133,6 +124,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + [[package]] name = "env_logger" version = "0.7.1" @@ -253,19 +250,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" -dependencies = [ - "cc", - "libc", - "log", - "rustc_version", - "winapi", -] - [[package]] name = "generic-array" version = "0.12.3" @@ -288,11 +272,12 @@ dependencies = [ [[package]] name = "gio" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd10f9415cce39b53f8024bf39a21f84f8157afa52da53837b102e585a296a5" +checksum = "3c5492e80b45e6c56214894a9a0cbe1340ab5066eb44a2dbe151393b6d7942c0" dependencies = [ "bitflags", + "futures", "futures-channel", "futures-core", "futures-io", @@ -301,27 +286,28 @@ dependencies = [ "glib", "glib-sys", "gobject-sys", - "lazy_static", "libc", + "once_cell", + "thiserror", ] [[package]] name = "gio-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fad225242b9eae7ec8a063bb86974aca56885014672375e5775dc0ea3533911" +checksum = "35993626299fbcaa73c0a19be8fdd01c950f9f3d3ac9cb4fb5532b924ab1a5d7" dependencies = [ "glib-sys", "gobject-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "glib" -version = "0.9.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" +checksum = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" dependencies = [ "bitflags", "futures-channel", @@ -329,38 +315,55 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", + "glib-macros", "glib-sys", "gobject-sys", - "lazy_static", "libc", + "once_cell", +] + +[[package]] +name = "glib-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" +dependencies = [ + "anyhow", + "heck", + "itertools", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "glib-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" +checksum = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" dependencies = [ "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gobject-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" +checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" dependencies = [ "glib-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer" -version = "0.15.7" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8664a114cd6ec16bece783d5eee59496919915b1f6884400ba4a953274a163" +checksum = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" dependencies = [ "bitflags", "cfg-if", @@ -371,18 +374,20 @@ dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", - "lazy_static", "libc", "muldiv", "num-rational", + "once_cell", "paste", + "pretty-hex", + "thiserror", ] [[package]] name = "gstreamer-sdp" -version = "0.15.6" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "547b3b0eb9e01e13ab5cc066c817e2ab758f83790145f80f62d3c8e43c2966af" +checksum = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" dependencies = [ "glib", "glib-sys", @@ -394,34 +399,34 @@ dependencies = [ [[package]] name = "gstreamer-sdp-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" +checksum = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" +checksum = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" dependencies = [ "glib-sys", "gobject-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer-webrtc" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" +checksum = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" dependencies = [ "glib", "glib-sys", @@ -435,16 +440,16 @@ dependencies = [ [[package]] name = "gstreamer-webrtc-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" +checksum = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sdp-sys", "gstreamer-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] @@ -458,9 +463,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" dependencies = [ "libc", ] @@ -511,6 +516,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.6" @@ -548,30 +562,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.71" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] -[[package]] -name = "loom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", -] - [[package]] name = "matches" version = "0.1.8" @@ -602,9 +605,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" dependencies = [ "autocfg", "num-integer", @@ -659,18 +662,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" dependencies = [ "proc-macro2", "quote", @@ -685,9 +688,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" [[package]] name = "ppv-lite86" @@ -695,6 +698,21 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "pretty-hex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.3" @@ -723,9 +741,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.16" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" [[package]] name = "proc-macro-nested" @@ -735,9 +753,9 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" dependencies = [ "unicode-xid", ] @@ -816,42 +834,12 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -[[package]] -name = "scoped-tls" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.114" @@ -871,9 +859,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.55" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" dependencies = [ "itoa", "ryu", @@ -923,10 +911,28 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.33" +name = "strum" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" + +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" dependencies = [ "proc-macro2", "quote", @@ -944,6 +950,21 @@ dependencies = [ "syn", ] +[[package]] +name = "system-deps" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" +dependencies = [ + "heck", + "pkg-config", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", +] + [[package]] name = "termcolor" version = "1.1.0" @@ -962,6 +983,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -977,6 +1018,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + [[package]] name = "tungstenite" version = "0.11.0" @@ -1028,9 +1078,9 @@ checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" @@ -1055,6 +1105,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + [[package]] name = "version_check" version = "0.9.2" diff --git a/webrtc/janus/rust/Cargo.toml b/webrtc/janus/rust/Cargo.toml index 29c6aad29d..417d959874 100644 --- a/webrtc/janus/rust/Cargo.toml +++ b/webrtc/janus/rust/Cargo.toml @@ -11,15 +11,15 @@ structopt = { version = "0.3", default-features = false } anyhow = "1" url = "2" rand = "0.7" -async-tungstenite = { version = "0.7", features = ["gio-runtime"] } -gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } -gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } -gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } +async-tungstenite = { version = "0.8", features = ["gio-runtime"] } +gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } +gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } +gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } serde = "1" serde_derive = "1" serde_json = "1.0.53" http = "0.2" -glib = "0.9" -gio = "0.8" +glib = "0.10" +gio = "0.9" log = "0.4.8" env_logger = "0.7.1" diff --git a/webrtc/janus/rust/src/janus.rs b/webrtc/janus/rust/src/janus.rs index e37adf80b2..2540f2205b 100644 --- a/webrtc/janus/rust/src/janus.rs +++ b/webrtc/janus/rust/src/janus.rs @@ -210,11 +210,11 @@ impl Peer { info!("starting negotiation with peer"); let peer_clone = self.downgrade(); - let promise = gst::Promise::new_with_change_func(move |res| { - let s = res.expect("no answer"); + let promise = gst::Promise::with_change_func(move |res| { + let s = res.ok().flatten().expect("no answer"); let peer = upgrade_weak!(peer_clone); - if let Err(err) = peer.on_offer_created(&s.to_owned()) { + if let Err(err) = peer.on_offer_created(s) { gst_element_error!( peer.bin, gst::LibraryError::Failed, @@ -231,7 +231,7 @@ impl Peer { // Once webrtcbin has create the offer SDP for us, handle it by sending it to the peer via the // WebSocket connection - fn on_offer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> { + fn on_offer_created(&self, reply: &gst::StructureRef) -> Result<(), anyhow::Error> { let offer = reply .get_value("offer")? .get::() @@ -328,8 +328,8 @@ impl Peer { .expect("Unable to set remote description"); let peer_clone = peer.downgrade(); - let promise = gst::Promise::new_with_change_func(move |reply| { - let s = reply.expect("No answer"); + let promise = gst::Promise::with_change_func(move |reply| { + let s = reply.ok().flatten().expect("No answer"); let peer = upgrade_weak!(peer_clone); if let Err(err) = peer.on_answer_created(&s.to_owned()) { @@ -493,8 +493,8 @@ impl JanusGateway { encoding_name=webrtc_codec.encoding_name ); - let encode_bin = gst::parse_bin_from_description(bin_description, false)?; - encode_bin.set_name("encode-bin")?; + let encode_bin = + gst::parse_bin_from_description_with_name(bin_description, false, "encode-bin")?; pipeline.add(&encode_bin).expect("Failed to add encode bin"); @@ -508,7 +508,7 @@ impl JanusGateway { .get_static_pad("sink") .expect("Failed to get sink pad from encoder"); - if let Ok(video_ghost_pad) = gst::GhostPad::new(Some("video_sink"), &sinkpad) { + if let Ok(video_ghost_pad) = gst::GhostPad::with_target(Some("video_sink"), &sinkpad) { encode_bin.add_pad(&video_ghost_pad)?; srcpad.link(&video_ghost_pad)?; } @@ -522,7 +522,8 @@ impl JanusGateway { let srcpad = vsink .get_static_pad("src") .expect("Element without src pad"); - if let Ok(webrtc_ghost_pad) = gst::GhostPad::new(Some("webrtc_video_src"), &srcpad) { + if let Ok(webrtc_ghost_pad) = gst::GhostPad::with_target(Some("webrtc_video_src"), &srcpad) + { encode_bin.add_pad(&webrtc_ghost_pad)?; webrtc_ghost_pad.link(&sinkpad2)?; } diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock index 4d0e83c5f9..a8ccdd26fd 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock @@ -2,67 +2,76 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "async-channel" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "event-listener 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "async-native-tls" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "kv-log-macro 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "smol 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "async-task" -version = "1.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "async-tungstenite" -version = "0.5.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "autocfg" version = "1.0.0" @@ -70,7 +79,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "base64" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -97,6 +106,24 @@ dependencies = [ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "blocking" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-channel 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atomic-waker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-lite 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byte-tools" version = "0.3.1" @@ -109,12 +136,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bytes" -version = "0.5.4" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cache-padded" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cc" -version = "1.0.54" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -129,7 +161,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "concurrent-queue" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cache-padded 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -138,7 +178,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -146,39 +186,6 @@ name = "core-foundation-sys" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "crossbeam-channel" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -197,11 +204,26 @@ dependencies = [ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "event-listener" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fastrand" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fnv" version = "1.0.7" @@ -220,20 +242,6 @@ name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "futures" version = "0.3.5" @@ -277,15 +285,29 @@ name = "futures-io" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-lite" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-macro" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -301,11 +323,6 @@ dependencies = [ "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "futures-timer" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "futures-util" version = "0.3.5" @@ -318,10 +335,10 @@ dependencies = [ "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-nested 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -339,13 +356,13 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib" -version = "0.9.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -354,34 +371,50 @@ dependencies = [ "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-macros 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glib-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "glib-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gobject-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer" -version = "0.15.5" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -389,79 +422,81 @@ dependencies = [ "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "paste 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-sdp" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-sdp-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-webrtc" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-webrtc-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gstreamer-webrtc-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -474,10 +509,10 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -485,9 +520,9 @@ name = "http" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -502,7 +537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -510,37 +545,36 @@ name = "input_buffer" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "itertools" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "js-sys" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "kv-log-macro" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -550,12 +584,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.70" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -566,63 +600,11 @@ name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "memoffset" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "muldiv" version = "0.2.1" @@ -634,49 +616,39 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "net2" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-integer" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-rational" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -687,8 +659,8 @@ name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -703,15 +675,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.10.29" +version = "0.10.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -721,34 +693,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.56" +version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "paste" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "paste-impl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "paste-impl" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -758,25 +732,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pin-project" -version = "0.4.17" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -786,7 +760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -795,53 +769,66 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "proc-macro-error" -version = "1.0.2" +name = "pretty-hex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-error-attr 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-error-attr" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-nested" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "1.0.15" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -850,7 +837,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -883,20 +870,20 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -905,12 +892,12 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "scoped-tls" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -921,7 +908,7 @@ dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -931,32 +918,32 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde" -version = "1.0.110" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.53" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -976,40 +963,82 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "smallvec" -version = "1.4.0" +name = "smol" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blocking 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wepoll-sys-stjepang 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "structopt" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strum" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "1.0.23" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1017,9 +1046,23 @@ name = "syn-mid" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "system-deps" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "version-compare 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1028,11 +1071,11 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1040,39 +1083,52 @@ name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thiserror" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thiserror-impl" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tungstenite" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1094,10 +1150,10 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1107,12 +1163,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1132,12 +1188,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vcpkg" -version = "0.2.8" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version-compare" +version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "waker-fn" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1145,43 +1211,110 @@ name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasm-bindgen" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "web-sys" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "webrtc-app" version = "0.1.0" dependencies = [ - "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-tungstenite 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "async-tungstenite 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-webrtc 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-webrtc 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wepoll-sys-stjepang" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1192,153 +1325,159 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [metadata] -"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +"checksum anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" +"checksum async-channel 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "958a8af017616083a7c739a9c4da4b757a6816593734b4b6145adbe1421526a5" "checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" -"checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" -"checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -"checksum async-tungstenite 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "182617e5bbfe7001b6f2317883506f239c77313171620a04cc11292704d3e171" +"checksum async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" +"checksum async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" +"checksum async-tungstenite 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" +"checksum atomic-waker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum blocking 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" +"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +"checksum bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +"checksum cache-padded 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +"checksum concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1582139bb74d97ef232c30bc236646017db06f13ee7cc01fa24c9e55640f86d4" "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum event-listener 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829694371bd7bbc6aee17c4ff624aad8bf9f4dc06c6f9f6071eaa08c89530d10" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" "checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" "checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" "checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" "checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +"checksum futures-lite 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" "checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" "checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" "checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" -"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" "checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum glib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" -"checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" -"checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" -"checksum gstreamer 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5918f609a4309c98347fc58b7259eac99fe5c69adcdd23cf617d967d7d4da1d1" -"checksum gstreamer-sdp 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9f2fcf072e733c9ecd29ce40df432148304995827b481a16e3a2adf57a6dfa" -"checksum gstreamer-sdp-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" -"checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" -"checksum gstreamer-webrtc 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" -"checksum gstreamer-webrtc-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" +"checksum glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" +"checksum glib-macros 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" +"checksum glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" +"checksum gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" +"checksum gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" +"checksum gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" +"checksum gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" +"checksum gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" +"checksum gstreamer-webrtc 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" +"checksum gstreamer-webrtc-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" "checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" +"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +"checksum js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" +"checksum kv-log-macro 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" -"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" -"checksum mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" "checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" -"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" -"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +"checksum num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" "checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" "checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" +"checksum openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)" = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)" = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" -"checksum paste 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" -"checksum paste-impl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" +"checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +"checksum parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" +"checksum paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +"checksum paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" -"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" -"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +"checksum pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +"checksum pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +"checksum pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" "checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +"checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" "checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" -"checksum proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" -"checksum proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" -"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" -"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" -"checksum proc-macro2 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" -"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +"checksum pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" +"checksum proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +"checksum proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" +"checksum proc-macro-error-attr 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" +"checksum proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)" = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" +"checksum proc-macro-nested 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" +"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" "checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" "checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" "checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" -"checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" -"checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" -"checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -"checksum structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" -"checksum structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" -"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" +"checksum smol 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" +"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +"checksum structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +"checksum structopt-derive 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +"checksum strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" +"checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +"checksum system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" -"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" -"checksum tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +"checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +"checksum tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" "checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +"checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +"checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +"checksum version-compare 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" +"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +"checksum waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" +"checksum wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" +"checksum wasm-bindgen-futures 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" +"checksum wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" +"checksum wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" +"checksum wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" +"checksum web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" +"checksum wepoll-sys-stjepang 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" +"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml index ed9812ed71..a6933ce229 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml @@ -10,10 +10,10 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.5", features = ["async-std-runtime", "async-native-tls"] } -gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } -gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } -gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } +async-tungstenite = { version = "0.8", features = ["async-std-runtime", "async-native-tls"] } +gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } +gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } +gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } serde = "1" serde_derive = "1" serde_json = "1" diff --git a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs index 995b4bd361..214a7d09de 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/src/main.rs +++ b/webrtc/multiparty-sendrecv/gst-rust/src/main.rs @@ -193,7 +193,7 @@ impl App { // Create a stream for handling the GStreamer message asynchronously let bus = pipeline.get_bus().unwrap(); - let send_gst_msg_rx = gst::BusStream::new(&bus); + let send_gst_msg_rx = bus.stream(); // Channel for outgoing WebSocket messages from other threads let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); @@ -338,7 +338,7 @@ impl App { let audio_queue = peer_bin .get_by_name("audio-queue") .expect("can't find audio-queue"); - let audio_sink_pad = gst::GhostPad::new( + let audio_sink_pad = gst::GhostPad::with_target( Some("audio_sink"), &audio_queue.get_static_pad("sink").unwrap(), ) @@ -348,7 +348,7 @@ impl App { let video_queue = peer_bin .get_by_name("video-queue") .expect("can't find video-queue"); - let video_sink_pad = gst::GhostPad::new( + let video_sink_pad = gst::GhostPad::with_target( Some("video_sink"), &video_queue.get_static_pad("sink").unwrap(), ) @@ -629,7 +629,7 @@ impl Peer { println!("starting negotiation with peer {}", self.peer_id); let peer_clone = self.downgrade(); - let promise = gst::Promise::new_with_change_func(move |reply| { + let promise = gst::Promise::with_change_func(move |reply| { let peer = upgrade_weak!(peer_clone); if let Err(err) = peer.on_offer_created(reply) { @@ -652,12 +652,15 @@ impl Peer { // WebSocket connection fn on_offer_created( &self, - reply: Result<&gst::StructureRef, gst::PromiseError>, + reply: Result, gst::PromiseError>, ) -> Result<(), anyhow::Error> { let reply = match reply { - Ok(reply) => reply, + Ok(Some(reply)) => reply, + Ok(None) => { + bail!("Offer creation future got no reponse"); + } Err(err) => { - bail!("Offer creation future got no reponse: {:?}", err); + bail!("Offer creation future got error reponse: {:?}", err); } }; @@ -698,12 +701,15 @@ impl Peer { // WebSocket connection fn on_answer_created( &self, - reply: Result<&gst::StructureRef, gst::PromiseError>, + reply: Result, gst::PromiseError>, ) -> Result<(), anyhow::Error> { let reply = match reply { - Ok(reply) => reply, + Ok(Some(reply)) => reply, + Ok(None) => { + bail!("Answer creation future got no reponse"); + } Err(err) => { - bail!("Answer creation future got no reponse: {:?}", err); + bail!("Answer creation future got error reponse: {:?}", err); } }; @@ -778,7 +784,7 @@ impl Peer { .unwrap(); let peer_clone = peer.downgrade(); - let promise = gst::Promise::new_with_change_func(move |reply| { + let promise = gst::Promise::with_change_func(move |reply| { let peer = upgrade_weak!(peer_clone); if let Err(err) = peer.on_answer_created(reply) { @@ -869,12 +875,14 @@ impl Peer { // Add a ghost pad on our conv bin that proxies the sink pad of the decodebin let dbin = conv.get_by_name("dbin").unwrap(); let sinkpad = - gst::GhostPad::new(Some("sink"), &dbin.get_static_pad("sink").unwrap()).unwrap(); + gst::GhostPad::with_target(Some("sink"), &dbin.get_static_pad("sink").unwrap()) + .unwrap(); conv.add_pad(&sinkpad).unwrap(); // And another one that proxies the source pad of the last element let src = conv.get_by_name("src").unwrap(); - let srcpad = gst::GhostPad::new(Some("src"), &src.get_static_pad("src").unwrap()).unwrap(); + let srcpad = + gst::GhostPad::with_target(Some("src"), &src.get_static_pad("src").unwrap()).unwrap(); conv.add_pad(&srcpad).unwrap(); self.bin.add(&conv).unwrap(); @@ -886,11 +894,11 @@ impl Peer { // And then add a new ghost pad to the peer bin that proxies the source pad we added above if media_type == "video" { - let srcpad = gst::GhostPad::new(Some("video_src"), &srcpad).unwrap(); + let srcpad = gst::GhostPad::with_target(Some("video_src"), &srcpad).unwrap(); srcpad.set_active(true).unwrap(); self.bin.add_pad(&srcpad).unwrap(); } else if media_type == "audio" { - let srcpad = gst::GhostPad::new(Some("audio_src"), &srcpad).unwrap(); + let srcpad = gst::GhostPad::with_target(Some("audio_src"), &srcpad).unwrap(); srcpad.set_active(true).unwrap(); self.bin.add_pad(&srcpad).unwrap(); } diff --git a/webrtc/sendrecv/gst-rust/Cargo.lock b/webrtc/sendrecv/gst-rust/Cargo.lock index cf2cbd7396..8cb99f87ca 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.lock +++ b/webrtc/sendrecv/gst-rust/Cargo.lock @@ -2,9 +2,20 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" + +[[package]] +name = "async-channel" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958a8af017616083a7c739a9c4da4b757a6816593734b4b6145adbe1421526a5" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] [[package]] name = "async-native-tls" @@ -20,44 +31,38 @@ dependencies = [ [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" dependencies = [ "async-task", - "crossbeam-channel", - "crossbeam-deque", "crossbeam-utils", + "futures-channel", "futures-core", "futures-io", - "futures-timer", "kv-log-macro", "log", "memchr", - "mio", - "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", + "smol", + "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "1.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -dependencies = [ - "libc", - "winapi 0.3.8", -] +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-tungstenite" -version = "0.5.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "182617e5bbfe7001b6f2317883506f239c77313171620a04cc11292704d3e171" +checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" dependencies = [ "async-native-tls", "async-std", @@ -68,6 +73,12 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "autocfg" version = "1.0.0" @@ -76,9 +87,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "base64" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bitflags" @@ -107,6 +118,26 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "blocking" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" +dependencies = [ + "async-channel", + "atomic-waker", + "futures-lite", + "once_cell", + "parking", + "waker-fn", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + [[package]] name = "byte-tools" version = "0.3.1" @@ -121,15 +152,21 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" -version = "1.0.54" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" [[package]] name = "cfg-if" @@ -148,6 +185,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1582139bb74d97ef232c30bc236646017db06f13ee7cc01fa24c9e55640f86d4" +dependencies = [ + "cache-padded", +] + [[package]] name = "core-foundation" version = "0.7.0" @@ -164,42 +210,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -[[package]] -name = "crossbeam-channel" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -dependencies = [ - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -220,12 +230,30 @@ dependencies = [ "generic-array", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "event-listener" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829694371bd7bbc6aee17c4ff624aad8bf9f4dc06c6f9f6071eaa08c89530d10" + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" + [[package]] name = "fnv" version = "1.0.7" @@ -247,22 +275,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.5" @@ -311,6 +323,21 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +[[package]] +name = "futures-lite" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.5" @@ -338,12 +365,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "futures-timer" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" - [[package]] name = "futures-util" version = "0.3.5" @@ -386,9 +407,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.9.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" +checksum = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" dependencies = [ "bitflags", "futures-channel", @@ -396,38 +417,55 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", + "glib-macros", "glib-sys", "gobject-sys", - "lazy_static", "libc", + "once_cell", +] + +[[package]] +name = "glib-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" +dependencies = [ + "anyhow", + "heck", + "itertools", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "glib-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" +checksum = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" dependencies = [ "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gobject-sys" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" +checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" dependencies = [ "glib-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer" -version = "0.15.5" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5918f609a4309c98347fc58b7259eac99fe5c69adcdd23cf617d967d7d4da1d1" +checksum = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" dependencies = [ "bitflags", "cfg-if", @@ -438,18 +476,20 @@ dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", - "lazy_static", "libc", "muldiv", "num-rational", + "once_cell", "paste", + "pretty-hex", + "thiserror", ] [[package]] name = "gstreamer-sdp" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9f2fcf072e733c9ecd29ce40df432148304995827b481a16e3a2adf57a6dfa" +checksum = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" dependencies = [ "glib", "glib-sys", @@ -461,34 +501,34 @@ dependencies = [ [[package]] name = "gstreamer-sdp-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a" +checksum = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" +checksum = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" dependencies = [ "glib-sys", "gobject-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] name = "gstreamer-webrtc" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1" +checksum = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" dependencies = [ "glib", "glib-sys", @@ -502,16 +542,16 @@ dependencies = [ [[package]] name = "gstreamer-webrtc-sys" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615" +checksum = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sdp-sys", "gstreamer-sys", "libc", - "pkg-config", + "system-deps", ] [[package]] @@ -525,9 +565,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" dependencies = [ "libc", ] @@ -570,35 +610,34 @@ dependencies = [ ] [[package]] -name = "iovec" -version = "0.1.4" +name = "itertools" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ - "libc", + "either", ] [[package]] name = "itoa" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "js-sys" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "wasm-bindgen", ] [[package]] name = "kv-log-macro" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] @@ -611,15 +650,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.70" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] @@ -630,69 +669,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -[[package]] -name = "memoffset" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mio" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" -dependencies = [ - "cfg-if", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "muldiv" version = "0.2.1" @@ -717,22 +699,11 @@ dependencies = [ "tempfile", ] -[[package]] -name = "net2" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" -dependencies = [ - "cfg-if", - "libc", - "winapi 0.3.8", -] - [[package]] name = "num-integer" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ "autocfg", "num-traits", @@ -740,9 +711,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" dependencies = [ "autocfg", "num-integer", @@ -751,9 +722,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg", ] @@ -782,9 +753,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" -version = "0.10.29" +version = "0.10.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ "bitflags", "cfg-if", @@ -802,9 +773,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.56" +version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ "autocfg", "cc", @@ -814,10 +785,16 @@ dependencies = [ ] [[package]] -name = "paste" -version = "0.1.12" +name = "parking" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" +checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" dependencies = [ "paste-impl", "proc-macro-hack", @@ -825,14 +802,11 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -843,18 +817,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.17" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" dependencies = [ "proc-macro2", "quote", @@ -863,9 +837,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" [[package]] name = "pin-utils" @@ -875,9 +849,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" [[package]] name = "ppv-lite86" @@ -886,10 +860,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] -name = "proc-macro-error" -version = "1.0.2" +name = "pretty-hex" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" +checksum = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -900,9 +889,9 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" +checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" dependencies = [ "proc-macro2", "quote", @@ -913,30 +902,30 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" [[package]] name = "proc-macro-nested" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.15" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -984,24 +973,24 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" @@ -1010,14 +999,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.8", + "winapi", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "scoped-tls" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "security-framework" @@ -1044,15 +1033,15 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.110" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" dependencies = [ "proc-macro2", "quote", @@ -1061,9 +1050,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.53" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" dependencies = [ "itoa", "ryu", @@ -1089,16 +1078,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] -name = "smallvec" -version = "1.4.0" +name = "smol" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" +dependencies = [ + "async-task", + "blocking", + "concurrent-queue", + "fastrand", + "futures-io", + "futures-util", + "libc", + "once_cell", + "scoped-tls", + "slab", + "socket2", + "wepoll-sys-stjepang", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] [[package]] name = "structopt" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" +checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" dependencies = [ "clap", "lazy_static", @@ -1107,9 +1123,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" +checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" dependencies = [ "heck", "proc-macro-error", @@ -1119,10 +1135,28 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.23" +name = "strum" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" + +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" dependencies = [ "proc-macro2", "quote", @@ -1140,6 +1174,21 @@ dependencies = [ "syn", ] +[[package]] +name = "system-deps" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" +dependencies = [ + "heck", + "pkg-config", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", +] + [[package]] name = "tempfile" version = "3.1.0" @@ -1151,7 +1200,7 @@ dependencies = [ "rand", "redox_syscall", "remove_dir_all", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -1165,18 +1214,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" dependencies = [ "proc-macro2", "quote", @@ -1184,10 +1233,25 @@ dependencies = [ ] [[package]] -name = "tungstenite" -version = "0.10.1" +name = "tinyvec" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "tungstenite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" dependencies = [ "base64", "byteorder", @@ -1219,11 +1283,11 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" dependencies = [ - "smallvec", + "tinyvec", ] [[package]] @@ -1234,15 +1298,15 @@ checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" @@ -1263,15 +1327,27 @@ checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" [[package]] name = "vcpkg" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "waker-fn" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" [[package]] name = "wasi" @@ -1279,6 +1355,82 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasm-bindgen" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" + +[[package]] +name = "web-sys" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webrtc-app" version = "0.1.0" @@ -1298,27 +1450,24 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.2.8" +name = "wepoll-sys-stjepang" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" +dependencies = [ + "cc", +] [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1330,13 +1479,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] diff --git a/webrtc/sendrecv/gst-rust/Cargo.toml b/webrtc/sendrecv/gst-rust/Cargo.toml index ed9812ed71..a6933ce229 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.toml +++ b/webrtc/sendrecv/gst-rust/Cargo.toml @@ -10,10 +10,10 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.5", features = ["async-std-runtime", "async-native-tls"] } -gst = { package = "gstreamer", version = "0.15", features = ["v1_14"] } -gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" } -gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] } +async-tungstenite = { version = "0.8", features = ["async-std-runtime", "async-native-tls"] } +gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } +gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } +gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } serde = "1" serde_derive = "1" serde_json = "1" diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index 92e71b29b6..961b97d382 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -137,7 +137,7 @@ impl App { // Create a stream for handling the GStreamer message asynchronously let bus = pipeline.get_bus().unwrap(); - let send_gst_msg_rx = gst::BusStream::new(&bus); + let send_gst_msg_rx = bus.stream(); // Channel for outgoing WebSocket messages from other threads let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); @@ -277,7 +277,7 @@ impl App { println!("starting negotiation"); let app_clone = self.downgrade(); - let promise = gst::Promise::new_with_change_func(move |reply| { + let promise = gst::Promise::with_change_func(move |reply| { let app = upgrade_weak!(app_clone); if let Err(err) = app.on_offer_created(reply) { @@ -300,12 +300,15 @@ impl App { // WebSocket connection fn on_offer_created( &self, - reply: Result<&gst::StructureRef, gst::PromiseError>, + reply: Result, gst::PromiseError>, ) -> Result<(), anyhow::Error> { let reply = match reply { - Ok(reply) => reply, + Ok(Some(reply)) => reply, + Ok(None) => { + bail!("Offer creation future got no reponse"); + } Err(err) => { - bail!("Offer creation future got no reponse: {:?}", err); + bail!("Offer creation future got error reponse: {:?}", err); } }; @@ -343,12 +346,15 @@ impl App { // WebSocket connection fn on_answer_created( &self, - reply: Result<&gst::StructureRef, gst::PromiseError>, + reply: Result, gst::PromiseError>, ) -> Result<(), anyhow::Error> { let reply = match reply { - Ok(reply) => reply, + Ok(Some(reply)) => reply, + Ok(None) => { + bail!("Answer creation future got no reponse"); + } Err(err) => { - bail!("Answer creation future got no reponse: {:?}", err); + bail!("Answer creation future got error reponse: {:?}", err); } }; @@ -420,7 +426,7 @@ impl App { .unwrap(); let app_clone = app.downgrade(); - let promise = gst::Promise::new_with_change_func(move |reply| { + let promise = gst::Promise::with_change_func(move |reply| { let app = upgrade_weak!(app_clone); if let Err(err) = app.on_answer_created(reply) { From 6378337a0ee7a539220fa909c8af30b077e71f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 31 Jul 2020 12:03:46 +0300 Subject: [PATCH 369/412] sendrecv/Rust: Only set pipeline to Playing after connecting to the signals Might miss some signal emissions otherwise, especially the on-negotiation-needed signal. Part-of: --- webrtc/sendrecv/gst-rust/src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index 961b97d382..904b0727f4 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -142,13 +142,6 @@ impl App { // Channel for outgoing WebSocket messages from other threads let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); - // Asynchronously set the pipeline to Playing - pipeline.call_async(|pipeline| { - pipeline - .set_state(gst::State::Playing) - .expect("Couldn't set pipeline to Playing"); - }); - let app = App(Arc::new(AppInner { args, pipeline, @@ -228,6 +221,13 @@ impl App { } }); + // Asynchronously set the pipeline to Playing + app.pipeline.call_async(|pipeline| { + pipeline + .set_state(gst::State::Playing) + .expect("Couldn't set pipeline to Playing"); + }); + Ok((app, send_gst_msg_rx, send_ws_msg_rx)) } From bbed24d91975696316892e687fafe35119444d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 5 Aug 2020 10:47:07 +0300 Subject: [PATCH 370/412] webrtc: Change H264 examples to use aggregate-mode=zero-latency for best compatibility The default changed back to none because it broke existing code. See https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/749 Part-of: --- webrtc/janus/janusvideoroom.py | 2 +- webrtc/janus/rust/src/janus.rs | 2 +- webrtc/sendonly/webrtc-unidirectional-h264.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/janus/janusvideoroom.py b/webrtc/janus/janusvideoroom.py index 37a6824434..1364864ee3 100644 --- a/webrtc/janus/janusvideoroom.py +++ b/webrtc/janus/janusvideoroom.py @@ -78,7 +78,7 @@ from gi.repository import GstSdp if DO_VP8: ( encoder, payloader, rtp_encoding) = ( "vp8enc target-bitrate=100000 overshoot=25 undershoot=100 deadline=33000 keyframe-max-dist=1", "rtpvp8pay picture-id-mode=2", "VP8" ) else: - ( encoder, payloader, rtp_encoding) = ( "x264enc", "rtph264pay", "H264" ) + ( encoder, payloader, rtp_encoding) = ( "x264enc", "rtph264pay aggregate-mode=zero-latency", "H264" ) PIPELINE_DESC = ''' webrtcbin name=sendrecv stun-server=stun://stun.l.google.com:19302 diff --git a/webrtc/janus/rust/src/janus.rs b/webrtc/janus/rust/src/janus.rs index 2540f2205b..dfecc9230f 100644 --- a/webrtc/janus/rust/src/janus.rs +++ b/webrtc/janus/rust/src/janus.rs @@ -66,7 +66,7 @@ const VP8: VideoParameter = VideoParameter { const H264: VideoParameter = VideoParameter { encoder: "x264enc tune=zerolatency", encoding_name: "H264", - payloader: "rtph264pay", + payloader: "rtph264pay aggregate-mode=zero-latency", }; impl std::str::FromStr for VideoParameter { diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index e297a534d5..4dba828fcb 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -179,7 +179,7 @@ create_receiver_entry (SoupWebsocketConnection * connection) gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " "v4l2src ! videorate ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " - "rtph264pay config-interval=-1 name=payloader ! " + "rtph264pay config-interval=-1 name=payloader aggregate-mode=zero-latency ! " "application/x-rtp,media=video,encoding-name=H264,payload=" RTP_PAYLOAD_TYPE " ! webrtcbin. ", &error); if (error != NULL) { From e1de93cf40677220a516e85969b8c7626cb66988 Mon Sep 17 00:00:00 2001 From: Carl Karsten Date: Sun, 9 Aug 2020 20:06:54 +0000 Subject: [PATCH 371/412] Update README.md Part-of: --- webrtc/signalling/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/signalling/README.md b/webrtc/signalling/README.md index 7b0573835d..e17e9380d5 100644 --- a/webrtc/signalling/README.md +++ b/webrtc/signalling/README.md @@ -13,7 +13,7 @@ In three separate tabs, run consecutively: ```console $ ./generate_cert.sh -$ ./simple-server.py +$ ./simple_server.py ``` ### Session Based From d1b81046a4ac7a95c5bfbb0fe607e8d7a6f52fa7 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 26 Jun 2020 12:34:31 +1000 Subject: [PATCH 372/412] webrtc/android: add missing gradle-wrapper jar Part-of: --- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53637 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 webrtc/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 webrtc/android/gradle/wrapper/gradle-wrapper.properties diff --git a/webrtc/android/gradle/wrapper/gradle-wrapper.jar b/webrtc/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..05ef575b0cd0173fc735f2857ce4bd594ce4f6bd GIT binary patch literal 53637 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 t9m@SZ!E*7&jsr?!t7TL-WYI4eM@gAug8 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 Date: Fri, 26 Jun 2020 13:05:17 +1000 Subject: [PATCH 373/412] webrtc/android: use the openssl Gio module That's what is shipped upstream now. Part-of: --- webrtc/android/app/src/main/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/android/app/src/main/jni/Android.mk b/webrtc/android/app/src/main/jni/Android.mk index db8bfca7c9..2ad389c39d 100644 --- a/webrtc/android/app/src/main/jni/Android.mk +++ b/webrtc/android/app/src/main/jni/Android.mk @@ -40,6 +40,6 @@ GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE_CUSTOM) $(GSTREAMER_PLUGIN GSTREAMER_EXTRA_DEPS := gstreamer-webrtc-1.0 gstreamer-sdp-1.0 gstreamer-video-1.0 libsoup-2.4 json-glib-1.0 glib-2.0 -G_IO_MODULES = gnutls +G_IO_MODULES = openssl include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk From a7daeb14c386e7017ba7f64e00a851112aed54a4 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 26 Jun 2020 13:29:53 +1000 Subject: [PATCH 374/412] webrtc/android: explicitly link to iconv As is now required Part-of: --- webrtc/android/app/src/main/jni/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/webrtc/android/app/src/main/jni/Android.mk b/webrtc/android/app/src/main/jni/Android.mk index 2ad389c39d..c0ab0f81d2 100644 --- a/webrtc/android/app/src/main/jni/Android.mk +++ b/webrtc/android/app/src/main/jni/Android.mk @@ -39,6 +39,7 @@ GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE_CUSTOM) $(GSTREAMER_PLUGIN $(GSTREAMER_PLUGINS_SYS) GSTREAMER_EXTRA_DEPS := gstreamer-webrtc-1.0 gstreamer-sdp-1.0 gstreamer-video-1.0 libsoup-2.4 json-glib-1.0 glib-2.0 +GSTREAMER_EXTRA_LIBS := -liconv G_IO_MODULES = openssl From 101d9965e54d380aa76f1c88372fc3374369ed9e Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 26 Jun 2020 16:17:44 +1000 Subject: [PATCH 375/412] webrtc/android: use a better name for the output apk Instead of a generic app-debug.apk Part-of: --- webrtc/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/android/app/build.gradle b/webrtc/android/app/build.gradle index 9092448743..7644e1e7e3 100644 --- a/webrtc/android/app/build.gradle +++ b/webrtc/android/app/build.gradle @@ -9,7 +9,7 @@ android { targetSdkVersion 15 versionCode 1 versionName "1.0" - + archivesBaseName = "$applicationId-v$versionCode" externalNativeBuild { ndkBuild { From 8b4d1567124a5ad6999cf00b90721bfdc659c261 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 26 Jun 2020 16:19:03 +1000 Subject: [PATCH 376/412] webrtc/android: initialize the debug category Fixes possible critical/crash on startup Part-of: --- webrtc/android/app/src/main/jni/Android.mk | 2 +- webrtc/android/app/src/main/jni/webrtc.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/webrtc/android/app/src/main/jni/Android.mk b/webrtc/android/app/src/main/jni/Android.mk index c0ab0f81d2..326781d6af 100644 --- a/webrtc/android/app/src/main/jni/Android.mk +++ b/webrtc/android/app/src/main/jni/Android.mk @@ -31,7 +31,7 @@ GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk -GSTREAMER_PLUGINS_CORE_CUSTOM := coreelements app audioconvert audiorate audioresample videoconvert videorate videoscale videotestsrc volume +GSTREAMER_PLUGINS_CORE_CUSTOM := coreelements app audioconvert audiorate audioresample videoconvert videorate videoscale videotestsrc volume GSTREAMER_PLUGINS_CODECS_CUSTOM := videoparsersbad vpx opus audioparsers opusparse androidmedia GSTREAMER_PLUGINS_NET_CUSTOM := tcp rtsp rtp rtpmanager udp srtp webrtc dtls nice GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE_CUSTOM) $(GSTREAMER_PLUGINS_CODECS_CUSTOM) $(GSTREAMER_PLUGINS_NET_CUSTOM) \ diff --git a/webrtc/android/app/src/main/jni/webrtc.c b/webrtc/android/app/src/main/jni/webrtc.c index e5b47fc5fc..7a30dd957a 100644 --- a/webrtc/android/app/src/main/jni/webrtc.c +++ b/webrtc/android/app/src/main/jni/webrtc.c @@ -822,6 +822,8 @@ native_class_init (JNIEnv * env, jclass klass) __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message); (*env)->ThrowNew (env, exception_class, message); } + GST_DEBUG_CATEGORY_INIT (debug_category, "webrtc", 0, + "GStreamer Android WebRTC"); //gst_debug_set_threshold_from_string ("gl*:7", FALSE); } @@ -905,7 +907,7 @@ JNI_OnLoad (JavaVM * vm, void *reserved) java_vm = vm; if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { - __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", + __android_log_print (ANDROID_LOG_ERROR, "GstWebRTC", "Could not retrieve JNIEnv"); return 0; } From 09195ebe86264970cded4b61af24d95f48bde920 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 19 Aug 2020 20:00:55 +1000 Subject: [PATCH 377/412] webrtc/android: add decodebin/autoaudiosink to plugin list Otherwise the app fails to run Part-of: --- webrtc/android/app/src/main/jni/Android.mk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webrtc/android/app/src/main/jni/Android.mk b/webrtc/android/app/src/main/jni/Android.mk index 326781d6af..a3b94da8a8 100644 --- a/webrtc/android/app/src/main/jni/Android.mk +++ b/webrtc/android/app/src/main/jni/Android.mk @@ -31,12 +31,13 @@ GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk -GSTREAMER_PLUGINS_CORE_CUSTOM := coreelements app audioconvert audiorate audioresample videoconvert videorate videoscale videotestsrc volume +GSTREAMER_PLUGINS_CORE_CUSTOM := coreelements app audioconvert audiorate audioresample videoconvert videorate videoscale videotestsrc audiotestsrc volume autodetect GSTREAMER_PLUGINS_CODECS_CUSTOM := videoparsersbad vpx opus audioparsers opusparse androidmedia GSTREAMER_PLUGINS_NET_CUSTOM := tcp rtsp rtp rtpmanager udp srtp webrtc dtls nice GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE_CUSTOM) $(GSTREAMER_PLUGINS_CODECS_CUSTOM) $(GSTREAMER_PLUGINS_NET_CUSTOM) \ $(GSTREAMER_PLUGINS_ENCODING) \ - $(GSTREAMER_PLUGINS_SYS) + $(GSTREAMER_PLUGINS_SYS) \ + $(GSTREAMER_PLUGINS_PLAYBACK) GSTREAMER_EXTRA_DEPS := gstreamer-webrtc-1.0 gstreamer-sdp-1.0 gstreamer-video-1.0 libsoup-2.4 json-glib-1.0 glib-2.0 GSTREAMER_EXTRA_LIBS := -liconv From 899cd55b5fa05dccd6bcce018fa13992d721687b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 20 Aug 2020 16:16:55 +0100 Subject: [PATCH 378/412] Release 1.17.90 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4111b71b50..4a837c2324 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.2.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.17.90', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 009290dc8784e6714dc2a29be123110c14a38123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 8 Sep 2020 00:10:23 +0100 Subject: [PATCH 379/412] Release 1.18.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4a837c2324..2700823879 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.17.90', license : 'LGPL') +project('gst-examples', 'c', version : '1.18.0', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 1f66cda890313d7accc016a2d5ec7d411d17244a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 8 Sep 2020 16:59:14 +0100 Subject: [PATCH 380/412] Back to development --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2700823879..150288ece8 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.18.0', license : 'LGPL') +project('gst-examples', 'c', version : '1.19.0.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From 43f8275ca9ea8108df03c1d07a6eb9049219b0e7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 19 Sep 2020 11:39:36 +0530 Subject: [PATCH 381/412] playback: Remove libvisual plugin from iOS GstPlayer example We won't be building the plugin in Cerbero anymore, so remove it from the iOS example too. See: https://gitlab.freedesktop.org/gstreamer/cerbero/-/merge_requests/605 Part-of: --- playback/player/ios/GstPlay/gst_ios_init.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/playback/player/ios/GstPlay/gst_ios_init.m b/playback/player/ios/GstPlay/gst_ios_init.m index f5d9f5f50e..3de37912b1 100644 --- a/playback/player/ios/GstPlay/gst_ios_init.m +++ b/playback/player/ios/GstPlay/gst_ios_init.m @@ -290,9 +290,6 @@ GST_PLUGIN_STATIC_DECLARE(rtspclientsink); #if defined(GST_IOS_PLUGIN_PLAYBACK) || defined(GST_IOS_PLUGINS_PLAYBACK) GST_PLUGIN_STATIC_DECLARE(playback); #endif -#if defined(GST_IOS_PLUGIN_LIBVISUAL) || defined(GST_IOS_PLUGINS_VIS) -GST_PLUGIN_STATIC_DECLARE(libvisual); -#endif #if defined(GST_IOS_PLUGIN_GOOM) || defined(GST_IOS_PLUGINS_VIS) GST_PLUGIN_STATIC_DECLARE(goom); #endif @@ -868,9 +865,6 @@ gst_ios_init (void) #if defined(GST_IOS_PLUGIN_PLAYBACK) || defined(GST_IOS_PLUGINS_PLAYBACK) GST_PLUGIN_STATIC_REGISTER(playback); #endif -#if defined(GST_IOS_PLUGIN_LIBVISUAL) || defined(GST_IOS_PLUGINS_VIS) - GST_PLUGIN_STATIC_REGISTER(libvisual); -#endif #if defined(GST_IOS_PLUGIN_GOOM) || defined(GST_IOS_PLUGINS_VIS) GST_PLUGIN_STATIC_REGISTER(goom); #endif From 4d0642800191d368d7342545ce75df3af3e2aab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Thu, 9 Jul 2020 16:30:41 -0400 Subject: [PATCH 382/412] webrtc sendonly: Exit on bus errors Catch bus errors and cleanly error out Part-of: --- webrtc/sendonly/webrtc-recvonly-h264.c | 41 ++++++++++++++++++- webrtc/sendonly/webrtc-unidirectional-h264.c | 42 +++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/webrtc/sendonly/webrtc-recvonly-h264.c b/webrtc/sendonly/webrtc-recvonly-h264.c index 1fc9a1dfa2..8df62f4fed 100644 --- a/webrtc/sendonly/webrtc-recvonly-h264.c +++ b/webrtc/sendonly/webrtc-recvonly-h264.c @@ -270,6 +270,38 @@ on_incoming_stream (GstElement * webrtc, GstPad * pad, gst_object_unref (sinkpad); } +static gboolean +bus_watch_cb (GstBus * bus, GstMessage * message, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_error (message, &error, &debug); + g_error ("Error on bus: %s (debug: %s)", error->message, debug); + g_error_free (error); + g_free (debug); + break; + } + case GST_MESSAGE_WARNING: + { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_warning (message, &error, &debug); + g_warning ("Warning on bus: %s (debug: %s)", error->message, debug); + g_error_free (error); + g_free (debug); + break; + } + default: + break; + } + + return G_SOURCE_CONTINUE; +} ReceiverEntry * create_receiver_entry (SoupWebsocketConnection * connection) @@ -278,6 +310,7 @@ create_receiver_entry (SoupWebsocketConnection * connection) ReceiverEntry *receiver_entry; GstCaps *video_caps; GstWebRTCRTPTransceiver *trans = NULL; + GstBus *bus; receiver_entry = g_slice_alloc0 (sizeof (ReceiverEntry)); receiver_entry->connection = connection; @@ -331,7 +364,13 @@ create_receiver_entry (SoupWebsocketConnection * connection) g_signal_connect (receiver_entry->webrtcbin, "on-ice-candidate", G_CALLBACK (on_ice_candidate_cb), (gpointer) receiver_entry); - gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING); + bus = gst_pipeline_get_bus (GST_PIPELINE (receiver_entry->pipeline)); + gst_bus_add_watch (bus, bus_watch_cb, NULL); + gst_object_unref (bus); + + if (gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) + g_error ("Error starting pipeline"); return receiver_entry; diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index 4dba828fcb..4f919c5bf8 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -158,6 +158,39 @@ const gchar *html_source = " \n \ \n \ "; +static gboolean +bus_watch_cb (GstBus * bus, GstMessage * message, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_error (message, &error, &debug); + g_error ("Error on bus: %s (debug: %s)", error->message, debug); + g_error_free (error); + g_free (debug); + break; + } + case GST_MESSAGE_WARNING: + { + GError *error = NULL; + gchar *debug = NULL; + + gst_message_parse_warning (message, &error, &debug); + g_warning ("Warning on bus: %s (debug: %s)", error->message, debug); + g_error_free (error); + g_free (debug); + break; + } + default: + break; + } + + return G_SOURCE_CONTINUE; +} + ReceiverEntry * create_receiver_entry (SoupWebsocketConnection * connection) { @@ -165,6 +198,7 @@ create_receiver_entry (SoupWebsocketConnection * connection) ReceiverEntry *receiver_entry; GstWebRTCRTPTransceiver *trans; GArray *transceivers; + GstBus *bus; receiver_entry = g_slice_alloc0 (sizeof (ReceiverEntry)); receiver_entry->connection = connection; @@ -205,7 +239,13 @@ create_receiver_entry (SoupWebsocketConnection * connection) g_signal_connect (receiver_entry->webrtcbin, "on-ice-candidate", G_CALLBACK (on_ice_candidate_cb), (gpointer) receiver_entry); - gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING); + bus = gst_pipeline_get_bus (GST_PIPELINE (receiver_entry->pipeline)); + gst_bus_add_watch (bus, bus_watch_cb, NULL); + gst_object_unref (bus); + + if (gst_element_set_state (receiver_entry->pipeline, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) + g_error ("Could not start pipeline"); return receiver_entry; From 992cb3c5f494eaa738a82ac09e4ab7a8abb6e7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Thu, 9 Jul 2020 16:31:37 -0400 Subject: [PATCH 383/412] webrtc sendonly: Add videoscale to avoid webcam compat issues Part-of: --- webrtc/sendonly/webrtc-unidirectional-h264.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index 4f919c5bf8..1e851b9cbe 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -212,7 +212,7 @@ create_receiver_entry (SoupWebsocketConnection * connection) receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " - "v4l2src ! videorate ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " + "v4l2src ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " "rtph264pay config-interval=-1 name=payloader aggregate-mode=zero-latency ! " "application/x-rtp,media=video,encoding-name=H264,payload=" RTP_PAYLOAD_TYPE " ! webrtcbin. ", &error); From 4e141f1076a6826e25081393db1cfe092dc47798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Thu, 9 Jul 2020 17:07:10 -0400 Subject: [PATCH 384/412] webrtc sendonly: Add priority to example Part-of: --- webrtc/sendonly/webrtc-unidirectional-h264.c | 70 ++++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index 1e851b9cbe..ea145c817e 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -15,17 +15,19 @@ #include #define RTP_PAYLOAD_TYPE "96" +#define RTP_AUDIO_PAYLOAD_TYPE "97" #define SOUP_HTTP_PORT 57778 #define STUN_SERVER "stun.l.google.com:19302" +gchar *video_priority = NULL; +gchar *audio_priority = NULL; + + typedef struct _ReceiverEntry ReceiverEntry; ReceiverEntry *create_receiver_entry (SoupWebsocketConnection * connection); void destroy_receiver_entry (gpointer receiver_entry_ptr); -GstPadProbeReturn payloader_caps_event_probe_cb (GstPad * pad, - GstPadProbeInfo * info, gpointer user_data); - void on_offer_created_cb (GstPromise * promise, gpointer user_data); void on_negotiation_needed_cb (GstElement * webrtcbin, gpointer user_data); void on_ice_candidate_cb (GstElement * webrtcbin, guint mline_index, @@ -191,6 +193,24 @@ bus_watch_cb (GstBus * bus, GstMessage * message, gpointer user_data) return G_SOURCE_CONTINUE; } +static GstWebRTCPriorityType +_priority_from_string (const gchar * s) +{ + GEnumClass *klass = + (GEnumClass *) g_type_class_ref (GST_TYPE_WEBRTC_PRIORITY_TYPE); + GEnumValue *en; + + g_return_val_if_fail (klass, 0); + if (!(en = g_enum_get_value_by_name (klass, s))) + en = g_enum_get_value_by_nick (klass, s); + g_type_class_unref (klass); + + if (en) + return en->value; + + return 0; +} + ReceiverEntry * create_receiver_entry (SoupWebsocketConnection * connection) { @@ -215,7 +235,9 @@ create_receiver_entry (SoupWebsocketConnection * connection) "v4l2src ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " "rtph264pay config-interval=-1 name=payloader aggregate-mode=zero-latency ! " "application/x-rtp,media=video,encoding-name=H264,payload=" - RTP_PAYLOAD_TYPE " ! webrtcbin. ", &error); + RTP_PAYLOAD_TYPE " ! webrtcbin. " + "autoaudiosrc is-live=1 ! queue max-size-buffers=1 leaky=downstream ! opusenc ! rtpopuspay pt=" + RTP_AUDIO_PAYLOAD_TYPE " ! webrtcbin. ", &error); if (error != NULL) { g_error ("Could not create WebRTC pipeline: %s\n", error->message); g_error_free (error); @@ -228,9 +250,25 @@ create_receiver_entry (SoupWebsocketConnection * connection) g_signal_emit_by_name (receiver_entry->webrtcbin, "get-transceivers", &transceivers); - g_assert (transceivers != NULL && transceivers->len > 0); + g_assert (transceivers != NULL && transceivers->len > 1); trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + if (video_priority) { + GstWebRTCPriorityType priority; + + priority = _priority_from_string (video_priority); + if (priority) + gst_webrtc_rtp_sender_set_priority (trans->sender, priority); + } + trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1); + trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + if (audio_priority) { + GstWebRTCPriorityType priority; + + priority = _priority_from_string (audio_priority); + if (priority) + gst_webrtc_rtp_sender_set_priority (trans->sender, priority); + } g_array_unref (transceivers); g_signal_connect (receiver_entry->webrtcbin, "on-negotiation-needed", @@ -583,15 +621,35 @@ exit_sighandler (gpointer user_data) } #endif + +static GOptionEntry entries[] = { + {"video-priority", 0, 0, G_OPTION_ARG_STRING, &video_priority, + "Priority of the video stream (very-low, low, medium or high)", + "PRIORITY"}, + {"audio-priority", 0, 0, G_OPTION_ARG_STRING, &audio_priority, + "Priority of the audio stream (very-low, low, medium or high)", + "PRIORITY"}, + {NULL}, +}; + int main (int argc, char *argv[]) { GMainLoop *mainloop; SoupServer *soup_server; GHashTable *receiver_entry_table; + GOptionContext *context; + GError *error = NULL; setlocale (LC_ALL, ""); - gst_init (&argc, &argv); + + context = g_option_context_new ("- gstreamer webrtc sendonly demo"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gst_init_get_option_group ()); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_printerr ("Error initializing: %s\n", error->message); + return -1; + } receiver_entry_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, From 3710c814323d8e9f908ba5ad03c213997e6fe699 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 23 Nov 2020 15:28:28 +0100 Subject: [PATCH 385/412] rust: Bump async-tungstenite This removes the pin-project 0.4 dependency to use 1.0 instead like the rest of the code. Part-of: --- webrtc/multiparty-sendrecv/gst-rust/Cargo.toml | 2 +- webrtc/sendrecv/gst-rust/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml index a6933ce229..eeeb282d82 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.toml @@ -10,7 +10,7 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.8", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.10", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } diff --git a/webrtc/sendrecv/gst-rust/Cargo.toml b/webrtc/sendrecv/gst-rust/Cargo.toml index a6933ce229..eeeb282d82 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.toml +++ b/webrtc/sendrecv/gst-rust/Cargo.toml @@ -10,7 +10,7 @@ async-std = "1" structopt = { version = "0.3", default-features = false } anyhow = "1" rand = "0.7" -async-tungstenite = { version = "0.8", features = ["async-std-runtime", "async-native-tls"] } +async-tungstenite = { version = "0.10", features = ["async-std-runtime", "async-native-tls"] } gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } From 20bc59f1ff4d9c0c2233045fcb4712a0bc0fd048 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 23 Nov 2020 15:29:31 +0100 Subject: [PATCH 386/412] rust: Regenerate Cargo.lock Part-of: --- .../multiparty-sendrecv/gst-rust/Cargo.lock | 1372 +++++++++-------- webrtc/sendrecv/gst-rust/Cargo.lock | 624 ++++---- 2 files changed, 1063 insertions(+), 933 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock index a8ccdd26fd..4e783a1916 100644 --- a/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock +++ b/webrtc/multiparty-sendrecv/gst-rust/Cargo.lock @@ -2,1482 +2,1546 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" [[package]] name = "async-channel" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-global-executor" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" +dependencies = [ + "async-executor", + "async-io", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0b2bb8ae20fede194e779150fe283f65a4a08461b496de546ec366b174ad9" dependencies = [ - "concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "event-listener 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", ] [[package]] name = "async-native-tls" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" dependencies = [ - "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std", + "native-tls", + "thiserror", + "url", ] [[package]] name = "async-std" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1" dependencies = [ - "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smol 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-futures 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "async-global-executor", + "async-io", + "async-mutex", + "blocking", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "3.0.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-tungstenite" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39eca8dd578b18e557361e50ca767df55c5e62f690a5e53868c3c7a8123145b7" dependencies = [ - "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project", + "tungstenite", ] [[package]] name = "atomic-waker" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "blocking" -version = "0.4.7" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" dependencies = [ - "async-channel 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atomic-waker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-lite 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", ] [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" -version = "1.0.58" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.1" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "textwrap", + "unicode-width", ] [[package]] name = "concurrent-queue" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ - "cache-padded 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cache-padded", ] [[package]] -name = "core-foundation" -version = "0.7.0" +name = "const_fn" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys", + "libc", ] [[package]] name = "core-foundation-sys" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cfg-if 1.0.0", + "const_fn", + "lazy_static", ] [[package]] name = "digest" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "either" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "event-listener" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fake-simd" -version = "0.1.2" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] [[package]] name = "futures" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] name = "futures-channel" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-executor" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-task", + "futures-util", ] [[package]] name = "futures-io" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-lite" -version = "0.1.10" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" dependencies = [ - "fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ - "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] name = "futures-task" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", ] [[package]] name = "futures-util" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", + "version_check", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "libc", + "wasi", ] [[package]] name = "glib" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-macros 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", ] [[package]] name = "glib-macros" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" dependencies = [ - "anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "heck", + "itertools", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "glib-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e9b997a66e9a23d073f2b1abb4dbfc3925e0b8952f67efd8d9b6e168e4cdc1" dependencies = [ - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "system-deps", +] + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "gobject-sys" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" dependencies = [ - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys", + "libc", + "system-deps", ] [[package]] name = "gstreamer" -version = "0.16.2" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d50f822055923f1cbede233aa5dfd4ee957cf328fb3076e330886094e11d6cf" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cfg-if 1.0.0", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "muldiv", + "num-rational", + "once_cell", + "paste", + "pretty-hex", + "thiserror", ] [[package]] name = "gstreamer-sdp" -version = "0.16.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36832b09858102a4def30d2eb0d599043914ef83674609d578feb80a0e6bd3f2" dependencies = [ - "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib", + "glib-sys", + "gobject-sys", + "gstreamer", + "gstreamer-sdp-sys", + "gstreamer-sys", ] [[package]] name = "gstreamer-sdp-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "405adf64f61c427c1c3cd6d928f0ca891bde2614b7bd31c3b3670ae0da3ac79a" dependencies = [ - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", ] [[package]] name = "gstreamer-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1f154082d01af5718c5f8a8eb4f565a4ea5586ad8833a8fc2c2aa6844b601d" dependencies = [ - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] name = "gstreamer-webrtc" -version = "0.16.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3077f2f250f493da8da786e8cfc160896963ec853219453748a88009768eeb" dependencies = [ - "glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-webrtc-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "glib", + "glib-sys", + "gobject-sys", + "gstreamer", + "gstreamer-sdp", + "gstreamer-sys", + "gstreamer-webrtc-sys", + "libc", ] [[package]] name = "gstreamer-webrtc-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f294eea3e1421c524e3067364ea2c750159df9395c6c93324fb8cab089bffd" dependencies = [ - "glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys", + "gobject-sys", + "gstreamer-sdp-sys", + "gstreamer-sys", + "libc", + "system-deps", ] [[package]] name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" dependencies = [ - "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "http" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ - "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "fnv", + "itoa", ] [[package]] name = "httparse" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" [[package]] name = "idna" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "input_buffer" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" dependencies = [ - "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", ] [[package]] name = "itertools" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "itoa" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "js-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" dependencies = [ - "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "log" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", ] [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "muldiv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" [[package]] name = "native-tls" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nb-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi", ] [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "num-traits", ] [[package]] name = "num-rational" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "num-integer", + "num-traits", ] [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] name = "once_cell" -version = "1.4.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "opaque-debug" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" version = "0.10.30" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cfg-if 0.1.10", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", ] [[package]] name = "openssl-probe" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] name = "parking" -version = "1.0.6" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "paste" -version = "0.1.18" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9" [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.23" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" dependencies = [ - "pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.23" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "polling" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "log", + "wepoll-sys", + "winapi", +] [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty-hex" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "toml", ] [[package]] name = "proc-macro-error" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "proc-macro-error-attr 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", ] [[package]] name = "proc-macro-error-attr" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", - "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core", ] [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "winapi", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "security-framework" -version = "0.4.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "0.4.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys", + "libc", ] [[package]] name = "serde" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" [[package]] name = "serde_derive" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ - "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "sha-1" -version = "0.8.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smol" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "blocking 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", - "wepoll-sys-stjepang 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "socket2" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "structopt" -version = "0.3.15" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" dependencies = [ - "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "lazy_static", + "structopt-derive", ] [[package]] name = "structopt-derive" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "strum" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" [[package]] name = "strum_macros" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "syn" -version = "1.0.36" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "system-deps" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", - "strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", - "strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "version-compare 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "heck", + "pkg-config", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "thiserror" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" dependencies = [ - "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tinyvec" -version = "0.3.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] name = "tungstenite" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" dependencies = [ - "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log", + "rand", + "sha-1", + "url", + "utf-8", ] [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", ] [[package]] name = "unicode-normalization" -version = "0.1.13" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ - "tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "form_urlencoded", + "idna", + "matches", + "percent-encoding", ] [[package]] name = "utf-8" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" [[package]] name = "vcpkg" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" [[package]] name = "version-compare" version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "waker-fn" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" dependencies = [ - "bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" dependencies = [ - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" [[package]] name = "web-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" dependencies = [ - "js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webrtc-app" version = "0.1.0" dependencies = [ - "anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "async-tungstenite 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-webrtc 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "async-std", + "async-tungstenite", + "futures", + "gstreamer", + "gstreamer-sdp", + "gstreamer-webrtc", + "rand", + "serde", + "serde_derive", + "serde_json", + "structopt", ] [[package]] -name = "wepoll-sys-stjepang" -version = "1.0.6" +name = "wepoll-sys" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" dependencies = [ - "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" -"checksum async-channel 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "958a8af017616083a7c739a9c4da4b757a6816593734b4b6145adbe1421526a5" -"checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" -"checksum async-std 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" -"checksum async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" -"checksum async-tungstenite 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" -"checksum atomic-waker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -"checksum blocking 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" -"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" -"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" -"checksum cache-padded 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" -"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" -"checksum concurrent-queue 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1582139bb74d97ef232c30bc236646017db06f13ee7cc01fa24c9e55640f86d4" -"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum event-listener 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829694371bd7bbc6aee17c4ff624aad8bf9f4dc06c6f9f6071eaa08c89530d10" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fastrand 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" -"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" -"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" -"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" -"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" -"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" -"checksum futures-lite 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" -"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" -"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" -"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" -"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum glib 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" -"checksum glib-macros 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" -"checksum glib-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" -"checksum gobject-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" -"checksum gstreamer 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" -"checksum gstreamer-sdp 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" -"checksum gstreamer-sdp-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" -"checksum gstreamer-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" -"checksum gstreamer-webrtc 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" -"checksum gstreamer-webrtc-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" -"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" -"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" -"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" -"checksum js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" -"checksum kv-log-macro 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" -"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum muldiv 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" -"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" -"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" -"checksum num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" -"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" -"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" -"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)" = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" -"checksum parking 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" -"checksum paste 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -"checksum paste-impl 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" -"checksum pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" -"checksum pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" -"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -"checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" -"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" -"checksum pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" -"checksum proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -"checksum proc-macro-error 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" -"checksum proc-macro-error-attr 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" -"checksum proc-macro-hack 0.5.18 (registry+https://github.com/rust-lang/crates.io-index)" = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" -"checksum proc-macro-nested 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" -"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" -"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" -"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" -"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" -"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" -"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" -"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" -"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smol 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" -"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -"checksum structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" -"checksum structopt-derive 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" -"checksum strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" -"checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" -"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" -"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -"checksum system-deps 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" -"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" -"checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" -"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -"checksum tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" -"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" -"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" -"checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" -"checksum version-compare 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" -"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" -"checksum waker-fn 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" -"checksum wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" -"checksum wasm-bindgen-futures 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" -"checksum wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" -"checksum wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" -"checksum wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" -"checksum web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" -"checksum wepoll-sys-stjepang 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" -"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/webrtc/sendrecv/gst-rust/Cargo.lock b/webrtc/sendrecv/gst-rust/Cargo.lock index 8cb99f87ca..4e783a1916 100644 --- a/webrtc/sendrecv/gst-rust/Cargo.lock +++ b/webrtc/sendrecv/gst-rust/Cargo.lock @@ -2,21 +2,77 @@ # It is not intended for manual editing. [[package]] name = "anyhow" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" [[package]] name = "async-channel" -version = "1.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958a8af017616083a7c739a9c4da4b757a6816593734b4b6145adbe1421526a5" +checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" dependencies = [ "concurrent-queue", "event-listener", "futures-core", ] +[[package]] +name = "async-executor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-global-executor" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" +dependencies = [ + "async-executor", + "async-io", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0b2bb8ae20fede194e779150fe283f65a4a08461b496de546ec366b174ad9" +dependencies = [ + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + [[package]] name = "async-native-tls" version = "0.3.3" @@ -31,15 +87,20 @@ dependencies = [ [[package]] name = "async-std" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" +checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1" dependencies = [ - "async-task", + "async-global-executor", + "async-io", + "async-mutex", + "blocking", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", + "futures-lite", + "gloo-timers", "kv-log-macro", "log", "memchr", @@ -48,21 +109,20 @@ dependencies = [ "pin-project-lite", "pin-utils", "slab", - "smol", "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "3.0.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-tungstenite" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" +checksum = "39eca8dd578b18e557361e50ca767df55c5e62f690a5e53868c3c7a8123145b7" dependencies = [ "async-native-tls", "async-std", @@ -81,9 +141,9 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" @@ -99,37 +159,25 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", "generic-array", ] -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - [[package]] name = "blocking" -version = "0.4.7" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" dependencies = [ "async-channel", + "async-task", "atomic-waker", + "fastrand", "futures-lite", "once_cell", - "parking", - "waker-fn", ] [[package]] @@ -138,12 +186,6 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.3.4" @@ -164,9 +206,9 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" -version = "1.0.58" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" [[package]] name = "cfg-if" @@ -175,10 +217,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] -name = "clap" -version = "2.33.1" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "bitflags", "textwrap", @@ -187,18 +235,24 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1582139bb74d97ef232c30bc236646017db06f13ee7cc01fa24c9e55640f86d4" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] [[package]] -name = "core-foundation" -version = "0.7.0" +name = "const_fn" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" dependencies = [ "core-foundation-sys", "libc", @@ -206,53 +260,57 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", + "const_fn", "lazy_static", ] [[package]] name = "digest" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ "generic-array", ] [[package]] name = "either" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "event-listener" -version = "2.2.1" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829694371bd7bbc6aee17c4ff624aad8bf9f4dc06c6f9f6071eaa08c89530d10" - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] [[package]] name = "fnv" @@ -276,10 +334,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "futures" -version = "0.3.5" +name = "form_urlencoded" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ "futures-channel", "futures-core", @@ -292,9 +360,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ "futures-core", "futures-sink", @@ -302,15 +370,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-executor" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ "futures-core", "futures-task", @@ -319,15 +387,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-lite" -version = "0.1.10" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" +checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" dependencies = [ "fastrand", "futures-core", @@ -340,9 +408,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -352,24 +420,24 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] name = "futures-task" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ "futures-channel", "futures-core", @@ -387,29 +455,30 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", + "version_check", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi", ] [[package]] name = "glib" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" +checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5" dependencies = [ "bitflags", "futures-channel", @@ -442,14 +511,27 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" +checksum = "c7e9b997a66e9a23d073f2b1abb4dbfc3925e0b8952f67efd8d9b6e168e4cdc1" dependencies = [ "libc", "system-deps", ] +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gobject-sys" version = "0.10.0" @@ -463,12 +545,12 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.16.2" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" +checksum = "5d50f822055923f1cbede233aa5dfd4ee957cf328fb3076e330886094e11d6cf" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 1.0.0", "futures-channel", "futures-core", "futures-util", @@ -487,9 +569,9 @@ dependencies = [ [[package]] name = "gstreamer-sdp" -version = "0.16.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" +checksum = "36832b09858102a4def30d2eb0d599043914ef83674609d578feb80a0e6bd3f2" dependencies = [ "glib", "glib-sys", @@ -501,9 +583,9 @@ dependencies = [ [[package]] name = "gstreamer-sdp-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" +checksum = "405adf64f61c427c1c3cd6d928f0ca891bde2614b7bd31c3b3670ae0da3ac79a" dependencies = [ "glib-sys", "gobject-sys", @@ -514,9 +596,9 @@ dependencies = [ [[package]] name = "gstreamer-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" +checksum = "fc1f154082d01af5718c5f8a8eb4f565a4ea5586ad8833a8fc2c2aa6844b601d" dependencies = [ "glib-sys", "gobject-sys", @@ -526,9 +608,9 @@ dependencies = [ [[package]] name = "gstreamer-webrtc" -version = "0.16.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" +checksum = "7b3077f2f250f493da8da786e8cfc160896963ec853219453748a88009768eeb" dependencies = [ "glib", "glib-sys", @@ -542,9 +624,9 @@ dependencies = [ [[package]] name = "gstreamer-webrtc-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" +checksum = "67f294eea3e1421c524e3067364ea2c750159df9395c6c93324fb8cab089bffd" dependencies = [ "glib-sys", "gobject-sys", @@ -565,9 +647,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ "libc", ] @@ -609,6 +691,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "itertools" version = "0.9.0" @@ -626,9 +717,9 @@ checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "js-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" dependencies = [ "wasm-bindgen", ] @@ -650,9 +741,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "log" @@ -660,7 +751,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -671,9 +762,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "muldiv" @@ -683,9 +774,9 @@ checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" [[package]] name = "native-tls" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f" dependencies = [ "lazy_static", "libc", @@ -700,10 +791,20 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.43" +name = "nb-connect" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -711,9 +812,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", @@ -722,9 +823,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -741,15 +842,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.4.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "opaque-debug" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" @@ -758,7 +859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 0.1.10", "foreign-types", "lazy_static", "libc", @@ -786,28 +887,15 @@ dependencies = [ [[package]] name = "parking" -version = "1.0.6" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "paste" -version = "0.1.18" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] +checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9" [[package]] name = "percent-encoding" @@ -817,18 +905,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.23" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.23" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2", "quote", @@ -837,9 +925,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" [[package]] name = "pin-utils" @@ -849,21 +937,34 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "polling" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "log", + "wepoll-sys", + "winapi", +] [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty-hex" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" +checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "proc-macro-crate" @@ -876,9 +977,9 @@ dependencies = [ [[package]] name = "proc-macro-error" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -889,22 +990,20 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "syn", - "syn-mid", "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" @@ -914,9 +1013,9 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] @@ -1002,17 +1101,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "security-framework" -version = "0.4.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" dependencies = [ "bitflags", "core-foundation", @@ -1023,9 +1116,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "0.4.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" dependencies = [ "core-foundation-sys", "libc", @@ -1033,15 +1126,15 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" [[package]] name = "serde_derive" -version = "1.0.114" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2", "quote", @@ -1050,9 +1143,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", @@ -1061,13 +1154,14 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.8.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" dependencies = [ "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", "digest", - "fake-simd", "opaque-debug", ] @@ -1077,44 +1171,11 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smol" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" -dependencies = [ - "async-task", - "blocking", - "concurrent-queue", - "fastrand", - "futures-io", - "futures-util", - "libc", - "once_cell", - "scoped-tls", - "slab", - "socket2", - "wepoll-sys-stjepang", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi", -] - [[package]] name = "structopt" -version = "0.3.15" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" dependencies = [ "clap", "lazy_static", @@ -1123,9 +1184,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" dependencies = [ "heck", "proc-macro-error", @@ -1154,26 +1215,15 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.36" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "system-deps" version = "1.3.2" @@ -1195,7 +1245,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "rand", "redox_syscall", @@ -1214,18 +1264,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ "proc-macro2", "quote", @@ -1234,24 +1284,33 @@ dependencies = [ [[package]] name = "tinyvec" -version = "0.3.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ "serde", ] [[package]] name = "tungstenite" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" +checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" dependencies = [ "base64", "byteorder", @@ -1283,18 +1342,18 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.13" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" [[package]] name = "unicode-width" @@ -1310,10 +1369,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" dependencies = [ + "form_urlencoded", "idna", "matches", "percent-encoding", @@ -1331,6 +1391,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" + [[package]] name = "version-compare" version = "0.0.10" @@ -1345,9 +1411,9 @@ checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "waker-fn" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasi" @@ -1357,19 +1423,19 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" dependencies = [ "bumpalo", "lazy_static", @@ -1382,11 +1448,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "js-sys", "wasm-bindgen", "web-sys", @@ -1394,9 +1460,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1404,9 +1470,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ "proc-macro2", "quote", @@ -1417,15 +1483,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" [[package]] name = "web-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" dependencies = [ "js-sys", "wasm-bindgen", @@ -1450,10 +1516,10 @@ dependencies = [ ] [[package]] -name = "wepoll-sys-stjepang" -version = "1.0.6" +name = "wepoll-sys" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" dependencies = [ "cc", ] From 753f14f5de06158a61fb840e3bf277d0aa5e9bd2 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Thu, 26 Nov 2020 02:41:53 +0900 Subject: [PATCH 387/412] sendrecv: Add an option for example to be able to accept connection request from peer Add "our-id" option to specify id to be used for registering to signalling server and wait connection request from peer Part-of: --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 86 ++++++++++++++++++++------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 8c4c9f0be5..9f24a29b1f 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -40,12 +40,13 @@ enum AppState }; static GMainLoop *loop; -static GstElement *pipe1, *webrtc1; +static GstElement *pipe1, *webrtc1 = NULL; static GObject *send_channel, *receive_channel; static SoupWebsocketConnection *ws_conn = NULL; static enum AppState app_state = 0; -static const gchar *peer_id = NULL; +static gchar *peer_id = NULL; +static gchar *our_id = NULL; static const gchar *server_url = "wss://webrtc.nirbheek.in:8443"; static gboolean disable_ssl = FALSE; static gboolean remote_is_offerer = FALSE; @@ -53,6 +54,8 @@ 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"}, + {"our-id", 0, 0, G_OPTION_ARG_STRING, &our_id, + "String ID of the session that peer can connect to us", "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}, @@ -75,12 +78,12 @@ cleanup_and_quit_loop (const gchar * msg, enum AppState state) /* This will call us again */ soup_websocket_connection_close (ws_conn, 1000, ""); else - g_object_unref (ws_conn); + g_clear_object (&ws_conn); } if (loop) { g_main_loop_quit (loop); - loop = NULL; + g_clear_pointer (&loop, g_main_loop_unref); } /* To allow usage as a GSourceFunc */ @@ -114,7 +117,7 @@ handle_media_stream (GstPad * pad, GstElement * pipe, const char *convert_name, GstElement *q, *conv, *resample, *sink; GstPadLinkReturn ret; - gst_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); + gst_println ("Trying to handle stream with %s ! %s", convert_name, sink_name); q = gst_element_factory_make ("queue", NULL); g_assert_nonnull (q); @@ -284,7 +287,7 @@ on_negotiation_needed (GstElement * element, gpointer user_data) { app_state = PEER_CALL_NEGOTIATING; - if (remote_is_offerer) { + if (remote_is_offerer || our_id) { gchar *msg = g_strdup_printf ("OFFER_REQUEST"); soup_websocket_connection_send_text (ws_conn, msg); g_free (msg); @@ -464,19 +467,28 @@ static gboolean register_with_server (void) { gchar *hello; - gint32 our_id; if (soup_websocket_connection_get_state (ws_conn) != SOUP_WEBSOCKET_STATE_OPEN) return FALSE; - our_id = g_random_int_range (10, 10000); - gst_print ("Registering id %i with server\n", our_id); + if (!our_id) { + gint32 id; + + id = g_random_int_range (10, 10000); + gst_print ("Registering id %i with server\n", id); + + hello = g_strdup_printf ("HELLO %i", id); + } else { + gst_print ("Registering id %s with server\n", our_id); + + hello = g_strdup_printf ("HELLO %s", our_id); + } + app_state = SERVER_REGISTERING; /* Register with the server with a random integer id. Reply will be received * by on_server_message() */ - hello = g_strdup_printf ("HELLO %i", our_id); soup_websocket_connection_send_text (ws_conn, hello); g_free (hello); @@ -572,10 +584,14 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, } app_state = SERVER_REGISTERED; gst_print ("Registered with server\n"); - /* Ask signalling server to connect us with a specific peer */ - if (!setup_call ()) { - cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); - goto out; + if (!our_id) { + /* Ask signalling server to connect us with a specific peer */ + if (!setup_call ()) { + cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); + goto out; + } + } else { + gst_println ("Waiting for connection from peer (our-id: %s)", our_id); } /* Call has been setup by the server, now we can start negotiation */ } else if (g_strcmp0 (text, "SESSION_OK") == 0) { @@ -628,6 +644,17 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, goto out; } + /* If peer connection wasn't made yet and we are expecting peer will + * connect to us, launch pipeline at this moment */ + if (!webrtc1 && our_id) { + if (!start_pipeline ()) { + cleanup_and_quit_loop ("ERROR: failed to start pipeline", + PEER_CALL_ERROR); + } + + app_state = PEER_CALL_NEGOTIATING; + } + object = json_node_get_object (root); /* Check type of JSON message */ if (json_object_has_member (object, "sdp")) { @@ -786,6 +813,7 @@ main (int argc, char *argv[]) { GOptionContext *context; GError *error = NULL; + int ret_code = -1; context = g_option_context_new ("- gstreamer webrtc sendrecv demo"); g_option_context_add_main_entries (context, entries, NULL); @@ -795,14 +823,22 @@ main (int argc, char *argv[]) return -1; } - if (!check_plugins ()) - return -1; - - if (!peer_id) { - gst_printerr ("--peer-id is a required argument\n"); - return -1; + if (!check_plugins ()) { + goto out; } + if (!peer_id && !our_id) { + gst_printerr ("--peer-id or --our-id is a required argument\n"); + goto out; + } + + if (peer_id && our_id) { + gst_printerr ("specify only --peer-id or --our-id\n"); + goto out; + } + + ret_code = 0; + /* Disable ssl when running a localhost server, because * it's probably a test server with a self-signed certificate */ { @@ -818,7 +854,9 @@ main (int argc, char *argv[]) connect_to_websocket_server_async (); g_main_loop_run (loop); - g_main_loop_unref (loop); + + if (loop) + g_main_loop_unref (loop); if (pipe1) { gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); @@ -826,5 +864,9 @@ main (int argc, char *argv[]) gst_object_unref (pipe1); } - return 0; +out: + g_free (peer_id); + g_free (our_id); + + return ret_code; } From 9e83c09be6e25f755a8209b9747c35f7cc7d1278 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Thu, 26 Nov 2020 02:34:48 +0900 Subject: [PATCH 388/412] sendrecv/js: Convert taps to spaces Part-of: --- webrtc/sendrecv/js/webrtc.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index 471085eeff..c532e099e3 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -120,11 +120,11 @@ function onServerMessage(event) { handleIncomingError(event.data); return; } - 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); - } + 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 { @@ -149,7 +149,7 @@ function onServerMessage(event) { } else { handleIncomingError("Unknown incoming JSON: " + msg); } - } + } } } @@ -302,13 +302,13 @@ function createCall(msg) { } peer_connection.onicecandidate = (event) => { - // We have a candidate, send it to the remote party with the - // same uuid - if (event.candidate == null) { - console.log("ICE Candidate was null, done"); - return; - } - ws_conn.send(JSON.stringify({'ice': event.candidate})); + // We have a candidate, send it to the remote party with the + // same uuid + if (event.candidate == null) { + console.log("ICE Candidate was null, done"); + return; + } + ws_conn.send(JSON.stringify({'ice': event.candidate})); }; if (msg != null) From 85aeda42fe6f0096553647f4b0c71fb0ca9af832 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Fri, 27 Nov 2020 18:16:52 +0900 Subject: [PATCH 389/412] sendrecv/js: Add an UI for connecting to specified peer id Part-of: --- webrtc/sendrecv/js/index.html | 7 +++++++ webrtc/sendrecv/js/webrtc.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html index 8325070e8a..462a53034c 100644 --- a/webrtc/sendrecv/js/index.html +++ b/webrtc/sendrecv/js/index.html @@ -26,6 +26,13 @@
Status: unknown
+
+
+ + + +
+
Our id is unknown

diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index c532e099e3..b646b88cee 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -25,6 +25,16 @@ var ws_conn; // Promise for local stream after constraints are approved by the user var local_stream_promise; +function onConnectClicked() { + var id = document.getElementById("peer-connect").value; + if (id == "") { + alert("Peer id must be filled out"); + return; + } + + ws_conn.send("SESSION " + id); +} + function getOurId() { return Math.floor(Math.random() * (9000 - 10) + 10).toString(); } @@ -115,6 +125,11 @@ function onServerMessage(event) { case "HELLO": setStatus("Registered with server, waiting for call"); return; + case "SESSION_OK": + setStatus("Starting negotiation"); + if (!peer_connection) + createCall(null).then (generateOffer); + return; default: if (event.data.startsWith("ERROR")) { handleIncomingError(event.data); From 767f46b1a0339e2729d21290c5f1940bc52ea941 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Thu, 10 Dec 2020 19:16:52 +0900 Subject: [PATCH 390/412] webrtc: sendonly: Add support for Windows Add meson build script and use mfvideosrc element in case of Windows Part-of: --- webrtc/meson.build | 1 + webrtc/sendonly/meson.build | 7 +++++++ webrtc/sendonly/webrtc-unidirectional-h264.c | 11 +++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 webrtc/sendonly/meson.build diff --git a/webrtc/meson.build b/webrtc/meson.build index 05fa6a4c82..5efc2ec68d 100644 --- a/webrtc/meson.build +++ b/webrtc/meson.build @@ -17,6 +17,7 @@ endif subdir('multiparty-sendrecv') subdir('signalling') +subdir('sendonly') subdir('sendrecv') subdir('check') diff --git a/webrtc/sendonly/meson.build b/webrtc/sendonly/meson.build new file mode 100644 index 0000000000..bb41eca0ee --- /dev/null +++ b/webrtc/sendonly/meson.build @@ -0,0 +1,7 @@ +executable('webrtc-recvonly-h264', + 'webrtc-recvonly-h264.c', + dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, libsoup_dep, json_glib_dep ]) + +executable('webrtc-unidirectional-h264', + 'webrtc-unidirectional-h264.c', + dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, libsoup_dep, json_glib_dep ]) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index ea145c817e..48fe8a05c2 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -19,6 +19,12 @@ #define SOUP_HTTP_PORT 57778 #define STUN_SERVER "stun.l.google.com:19302" +#ifdef G_OS_WIN32 +#define VIDEO_SRC "mfvideosrc" +#else +#define VIDEO_SRC "v4l2src" +#endif + gchar *video_priority = NULL; gchar *audio_priority = NULL; @@ -232,11 +238,12 @@ create_receiver_entry (SoupWebsocketConnection * connection) receiver_entry->pipeline = gst_parse_launch ("webrtcbin name=webrtcbin stun-server=stun://" STUN_SERVER " " - "v4l2src ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " + VIDEO_SRC + " ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=15/1 ! videoconvert ! queue max-size-buffers=1 ! x264enc bitrate=600 speed-preset=ultrafast tune=zerolatency key-int-max=15 ! video/x-h264,profile=constrained-baseline ! queue max-size-time=100000000 ! h264parse ! " "rtph264pay config-interval=-1 name=payloader aggregate-mode=zero-latency ! " "application/x-rtp,media=video,encoding-name=H264,payload=" RTP_PAYLOAD_TYPE " ! webrtcbin. " - "autoaudiosrc is-live=1 ! queue max-size-buffers=1 leaky=downstream ! opusenc ! rtpopuspay pt=" + "autoaudiosrc is-live=1 ! queue max-size-buffers=1 leaky=downstream ! audioconvert ! audioresample ! opusenc ! rtpopuspay pt=" RTP_AUDIO_PAYLOAD_TYPE " ! webrtcbin. ", &error); if (error != NULL) { g_error ("Could not create WebRTC pipeline: %s\n", error->message); From a508bc243d02c256c1d6ac7fbe6e6980f655e70f Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 14:27:03 +0530 Subject: [PATCH 391/412] sendrecv/gst: Don't need to allocate to send OFFER_REQUEST Part-of: --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 9f24a29b1f..68469d5183 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -288,9 +288,7 @@ on_negotiation_needed (GstElement * element, gpointer user_data) app_state = PEER_CALL_NEGOTIATING; if (remote_is_offerer || our_id) { - gchar *msg = g_strdup_printf ("OFFER_REQUEST"); - soup_websocket_connection_send_text (ws_conn, msg); - g_free (msg); + soup_websocket_connection_send_text (ws_conn, "OFFER_REQUEST"); } else { GstPromise *promise; promise = From ea3c0e87660aa987881b4c7c3424fbf56e709c54 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 14:27:31 +0530 Subject: [PATCH 392/412] sendrecv/js: Handle OFFER_REQUEST as part of the switch This is clearer, and also stricter w.r.t. what sort of messages we accept. Part-of: --- webrtc/sendrecv/js/webrtc.js | 52 +++++++++++++++++------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index b646b88cee..6f893e7189 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -130,40 +130,38 @@ function onServerMessage(event) { if (!peer_connection) createCall(null).then (generateOffer); return; + case "OFFER_REQUEST": + // The peer wants us to set up and then send an offer + if (!peer_connection) + createCall(null).then (generateOffer); + return; default: if (event.data.startsWith("ERROR")) { handleIncomingError(event.data); return; } - 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; - } - - // 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); + // 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 incoming JSON: " + msg); + handleIncomingError("Unknown error parsing response: " + event.data); } + return; + } + + // 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); } } } From eb89cd01ba10ce1dd7f31028f59eab65e37ec2e8 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 14:32:13 +0530 Subject: [PATCH 393/412] webrtc: Document OFFER_REQUEST in the protocol doc Part-of: --- webrtc/signalling/Protocol.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrtc/signalling/Protocol.md b/webrtc/signalling/Protocol.md index 4a86868138..26f80d04b4 100644 --- a/webrtc/signalling/Protocol.md +++ b/webrtc/signalling/Protocol.md @@ -35,7 +35,8 @@ This protocol builds upon https://github.com/shanet/WebRTC-Example/ * To connect to a single peer, send `SESSION ` where `` identifies the peer to connect to, and receive `SESSION_OK` * All further messages will be forwarded to the peer -* The call negotiation with the peer can be started by sending JSON encoded SDP and ICE +* The call negotiation with the peer can be started by sending JSON encoded SDP (the offer) and ICE +* You can also ask the peer to send the SDP offer and begin sending ICE candidates. After `SESSION_OK` if you send `OFFER_REQUEST`, the peer will take over. (NEW in 1.19, not all clients support this) * Closure of the server connection means the call has ended; either because the other peer ended it or went away * To end the call, disconnect from the server. You may reconnect again whenever you wish. From 2892a8b206ea906d20ceefd08fc4fdffc9d80eb7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 15:16:11 +0530 Subject: [PATCH 394/412] sendrecv/js: Implement state handling for Connect button Part-of: --- webrtc/sendrecv/js/index.html | 2 +- webrtc/sendrecv/js/webrtc.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html index 462a53034c..772b11996d 100644 --- a/webrtc/sendrecv/js/index.html +++ b/webrtc/sendrecv/js/index.html @@ -30,7 +30,7 @@
- +
Our id is unknown
diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index 6f893e7189..433462fb0c 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -25,7 +25,16 @@ var ws_conn; // Promise for local stream after constraints are approved by the user var local_stream_promise; +function setConnectButtonState(value) { + document.getElementById("peer-connect-button").value = value; +} + function onConnectClicked() { + if (document.getElementById("peer-connect-button").value == "Disconnect") { + resetState(); + return; + } + var id = document.getElementById("peer-connect").value; if (id == "") { alert("Peer id must be filled out"); @@ -33,6 +42,7 @@ function onConnectClicked() { } ws_conn.send("SESSION " + id); + setConnectButtonState("Disconnect"); } function getOurId() { @@ -237,6 +247,7 @@ function websocketServerConnect() { document.getElementById("peer-id").textContent = peer_id; ws_conn.send('HELLO ' + peer_id); setStatus("Registering with server"); + setConnectButtonState("Connect"); }); ws_conn.addEventListener('error', onServerError); ws_conn.addEventListener('message', onServerMessage); @@ -327,5 +338,6 @@ function createCall(msg) { if (msg != null) setStatus("Created peer connection for call, waiting for SDP"); + setConnectButtonState("Disconnect"); return local_stream_promise; } From 28aa23dc20dd7e3dab8d193cbe554ab8eb41f9ae Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 15:28:35 +0530 Subject: [PATCH 395/412] sendrecv/gst: Some misc whitespace fixes Part-of: --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 68469d5183..e16a8af8ad 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -290,9 +290,8 @@ on_negotiation_needed (GstElement * element, gpointer user_data) if (remote_is_offerer || our_id) { soup_websocket_connection_send_text (ws_conn, "OFFER_REQUEST"); } else { - GstPromise *promise; - promise = - gst_promise_new_with_change_func (on_offer_created, user_data, NULL);; + GstPromise *promise = + gst_promise_new_with_change_func (on_offer_created, NULL, NULL); g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); } } @@ -573,8 +572,8 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, g_assert_not_reached (); } - /* Server has accepted our registration, we are ready to send commands */ if (g_strcmp0 (text, "HELLO") == 0) { + /* Server has accepted our registration, we are ready to send commands */ if (app_state != SERVER_REGISTERING) { cleanup_and_quit_loop ("ERROR: Received HELLO when not registering", APP_STATE_ERROR); @@ -591,8 +590,9 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, } else { gst_println ("Waiting for connection from peer (our-id: %s)", our_id); } - /* Call has been setup by the server, now we can start negotiation */ } else if (g_strcmp0 (text, "SESSION_OK") == 0) { + /* The call initiated by us has been setup by the server; now we can start + * negotiation */ if (app_state != PEER_CONNECTING) { cleanup_and_quit_loop ("ERROR: Received SESSION_OK when not calling", PEER_CONNECTION_ERROR); @@ -604,8 +604,8 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, if (!start_pipeline ()) cleanup_and_quit_loop ("ERROR: failed to start pipeline", PEER_CALL_ERROR); - /* Handle errors */ } else if (g_str_has_prefix (text, "ERROR")) { + /* Handle errors */ switch (app_state) { case SERVER_CONNECTING: app_state = SERVER_CONNECTION_ERROR; @@ -624,20 +624,20 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, app_state = APP_STATE_ERROR; } cleanup_and_quit_loop (text, 0); - /* Look for JSON messages containing SDP and ICE candidates */ } else { + /* Look for JSON messages containing SDP and ICE candidates */ JsonNode *root; JsonObject *object, *child; JsonParser *parser = json_parser_new (); if (!json_parser_load_from_data (parser, text, -1, NULL)) { - gst_printerr ("Unknown message '%s', ignoring", text); + gst_printerr ("Unknown message '%s', ignoring\n", text); g_object_unref (parser); goto out; } root = json_parser_get_root (parser); if (!JSON_NODE_HOLDS_OBJECT (root)) { - gst_printerr ("Unknown json message '%s', ignoring", text); + gst_printerr ("Unknown json message '%s', ignoring\n", text); g_object_unref (parser); goto out; } From f8cbae9d6eb85381c4428998880dfcd863e205d3 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 9 Feb 2021 15:28:57 +0530 Subject: [PATCH 396/412] sendrecv: Implement remote-offerer option for JS example Now you can check the "Remote offerer" checkbox in the JS example to force the peer to send the SDP offer. This involved implementing support for receiving the OFFER_REQUEST message in the C example. As a side-effect of this, the C example will no longer send OFFER_REQUEST automatically when the --our-id option is passed. It will only do so when the --remote-offerer option is explicitly passed. Part-of: --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 23 +++++++++++++++++------ webrtc/sendrecv/js/index.html | 3 +++ webrtc/sendrecv/js/webrtc.js | 9 +++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index e16a8af8ad..709ac70102 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -285,11 +285,12 @@ on_offer_created (GstPromise * promise, gpointer user_data) static void on_negotiation_needed (GstElement * element, gpointer user_data) { + gboolean create_offer = GPOINTER_TO_INT (user_data); app_state = PEER_CALL_NEGOTIATING; - if (remote_is_offerer || our_id) { + if (remote_is_offerer) { soup_websocket_connection_send_text (ws_conn, "OFFER_REQUEST"); - } else { + } else if (create_offer) { GstPromise *promise = gst_promise_new_with_change_func (on_offer_created, NULL, NULL); g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); @@ -372,7 +373,7 @@ on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec, } static gboolean -start_pipeline (void) +start_pipeline (gboolean create_offer) { GstStateChangeReturn ret; GError *error = NULL; @@ -397,7 +398,7 @@ start_pipeline (void) /* This is the gstwebrtc entry point where we create the offer and so on. It * will be called when the pipeline goes to PLAYING. */ g_signal_connect (webrtc1, "on-negotiation-needed", - G_CALLBACK (on_negotiation_needed), NULL); + G_CALLBACK (on_negotiation_needed), GINT_TO_POINTER (create_offer)); /* We need to transmit this ICE candidate to the browser via the websockets * signalling server. Incoming ice candidates from the browser need to be * added by us too, see on_server_message() */ @@ -601,7 +602,17 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, app_state = PEER_CONNECTED; /* Start negotiation (exchange SDP and ICE candidates) */ - if (!start_pipeline ()) + if (!start_pipeline (TRUE)) + cleanup_and_quit_loop ("ERROR: failed to start pipeline", + PEER_CALL_ERROR); + } else if (g_strcmp0 (text, "OFFER_REQUEST") == 0) { + if (app_state != SERVER_REGISTERED) { + gst_printerr ("Received OFFER_REQUEST at a strange time, ignoring\n"); + goto out; + } + gst_print ("Received OFFER_REQUEST, sending offer\n"); + /* Peer wants us to start negotiation (exchange SDP and ICE candidates) */ + if (!start_pipeline (TRUE)) cleanup_and_quit_loop ("ERROR: failed to start pipeline", PEER_CALL_ERROR); } else if (g_str_has_prefix (text, "ERROR")) { @@ -645,7 +656,7 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, /* If peer connection wasn't made yet and we are expecting peer will * connect to us, launch pipeline at this moment */ if (!webrtc1 && our_id) { - if (!start_pipeline ()) { + if (!start_pipeline (FALSE)) { cleanup_and_quit_loop ("ERROR: failed to start pipeline", PEER_CALL_ERROR); } diff --git a/webrtc/sendrecv/js/index.html b/webrtc/sendrecv/js/index.html index 772b11996d..2c5f65ee7c 100644 --- a/webrtc/sendrecv/js/index.html +++ b/webrtc/sendrecv/js/index.html @@ -31,6 +31,9 @@ + + Remote offerer
Our id is unknown
diff --git a/webrtc/sendrecv/js/webrtc.js b/webrtc/sendrecv/js/webrtc.js index 433462fb0c..02d1ea046e 100644 --- a/webrtc/sendrecv/js/webrtc.js +++ b/webrtc/sendrecv/js/webrtc.js @@ -29,6 +29,10 @@ function setConnectButtonState(value) { document.getElementById("peer-connect-button").value = value; } +function wantRemoteOfferer() { + return document.getElementById("remote-offerer").checked; +} + function onConnectClicked() { if (document.getElementById("peer-connect-button").value == "Disconnect") { resetState(); @@ -137,6 +141,11 @@ function onServerMessage(event) { return; case "SESSION_OK": setStatus("Starting negotiation"); + if (wantRemoteOfferer()) { + ws_conn.send("OFFER_REQUEST"); + setStatus("Sent OFFER_REQUEST, waiting for offer"); + return; + } if (!peer_connection) createCall(null).then (generateOffer); return; From d5a183cc2fc1350adb065789b2e81736c0ad9e66 Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Tue, 28 Apr 2020 23:12:44 +0200 Subject: [PATCH 397/412] gst-play: use novel signal-adapter (requires gstplayer lib patch from https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/35) Part-of: --- playback/player/gst-play/gst-play.c | 32 ++++++++++++++++------------- playback/player/gtk/gtk-play.c | 3 +-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index b7b98e1fd8..62aa2cdf5c 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -357,24 +357,28 @@ play_new (gchar ** uris, gdouble initial_volume) play->cur_idx = -1; play->player = - gst_player_new (NULL, gst_player_g_main_context_signal_dispatcher_new - (NULL)); - - g_signal_connect (play->player, "position-updated", - G_CALLBACK (position_updated_cb), play); - g_signal_connect (play->player, "state-changed", - G_CALLBACK (state_changed_cb), play); - g_signal_connect (play->player, "buffering", G_CALLBACK (buffering_cb), play); - g_signal_connect (play->player, "end-of-stream", - G_CALLBACK (end_of_stream_cb), play); - g_signal_connect (play->player, "error", G_CALLBACK (error_cb), play); - - g_signal_connect (play->player, "media-info-updated", - G_CALLBACK (media_info_cb), play); + gst_player_new (NULL); play->loop = g_main_loop_new (NULL, FALSE); play->desired_state = GST_STATE_PLAYING; + GstPlayerSignalAdapter *player_sig_adapt + = gst_player_signal_adapter_new( + gst_player_get_message_bus (play->player), + g_main_loop_get_context (play->loop)); + + g_signal_connect (player_sig_adapt, "position-updated", + G_CALLBACK (position_updated_cb), play); + g_signal_connect (player_sig_adapt, "state-changed", + G_CALLBACK (state_changed_cb), play); + g_signal_connect (player_sig_adapt, "buffering", G_CALLBACK (buffering_cb), play); + g_signal_connect (player_sig_adapt, "end-of-stream", + G_CALLBACK (end_of_stream_cb), play); + g_signal_connect (player_sig_adapt, "error", G_CALLBACK (error_cb), play); + + g_signal_connect (player_sig_adapt, "media-info-updated", + G_CALLBACK (media_info_cb), play); + play_set_relative_volume (play, initial_volume - 1.0); return play; diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index 786458e298..c6b1b42365 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -1743,8 +1743,7 @@ gtk_play_constructor (GType type, guint n_construct_params, create_ui (self); self->player = - gst_player_new (self->renderer, - gst_player_g_main_context_signal_dispatcher_new (NULL)); + gst_player_new (self->renderer); g_signal_connect (self->player, "position-updated", G_CALLBACK (position_updated_cb), self); From 50e61f52ed2bc6368f0ee7b8423dcf43ee5d034d Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 13 May 2020 05:41:36 +0200 Subject: [PATCH 398/412] gst-play.c: update to signal-adapter constructor change Part-of: --- playback/player/gst-play/gst-play.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index 62aa2cdf5c..b28646f88b 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -364,7 +364,7 @@ play_new (gchar ** uris, gdouble initial_volume) GstPlayerSignalAdapter *player_sig_adapt = gst_player_signal_adapter_new( - gst_player_get_message_bus (play->player), + play->player, g_main_loop_get_context (play->loop)); g_signal_connect (player_sig_adapt, "position-updated", From 840fcf43f52c47cfd886720a3789751a7977a05a Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Sat, 14 Nov 2020 11:01:01 +0000 Subject: [PATCH 399/412] gst-play: Port to GstPlay Part-of: --- meson.build | 4 +- playback/player/gst-play/gst-play.c | 208 ++++++++++++++------------- playback/player/gst-play/meson.build | 2 +- 3 files changed, 109 insertions(+), 105 deletions(-) diff --git a/meson.build b/meson.build index 150288ece8..3f0f994d7f 100644 --- a/meson.build +++ b/meson.build @@ -20,8 +20,8 @@ gmodule_dep = dependency('gmodule-2.0', gst_dep = dependency('gstreamer-1.0', fallback : ['gstreamer', 'gst_dep']) -gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.7.1.1', - fallback: ['gst-plugins-bad', 'gstplayer_dep']) +gstplay_dep = dependency('gstreamer-play-1.0', version: '>= 1.19.0.1', + fallback: ['gst-plugins-bad', 'gstplay_dep']) gsttag_dep = dependency('gstreamer-tag-1.0', fallback: ['gst-plugins-base', 'tag_dep']) gstwebrtc_dep = dependency('gstreamer-webrtc-1.0', version: '>= 1.14.0', diff --git a/playback/player/gst-play/gst-play.c b/playback/player/gst-play/gst-play.c index b28646f88b..73755ffaa1 100644 --- a/playback/player/gst-play/gst-play.c +++ b/playback/player/gst-play/gst-play.c @@ -30,7 +30,7 @@ #include #include "gst-play-kb.h" -#include +#include #define VOLUME_STEPS 20 @@ -43,21 +43,22 @@ typedef struct guint num_uris; gint cur_idx; - GstPlayer *player; + GstPlay *player; + GstPlaySignalAdapter *signal_adapter; GstState desired_state; gboolean repeat; GMainLoop *loop; -} GstPlay; +} Player; -static gboolean play_next (GstPlay * play); -static gboolean play_prev (GstPlay * play); -static void play_reset (GstPlay * play); -static void play_set_relative_volume (GstPlay * play, gdouble volume_step); +static gboolean play_next (Player * play); +static gboolean play_prev (Player * play); +static void play_reset (Player * play); +static void play_set_relative_volume (Player * play, gdouble volume_step); static void -end_of_stream_cb (GstPlayer * player, GstPlay * play) +end_of_stream_cb (GstPlaySignalAdapter * adapter, Player * play) { gst_print ("\n"); /* and switch to next item in list */ @@ -68,7 +69,7 @@ end_of_stream_cb (GstPlayer * player, GstPlay * play) } static void -error_cb (GstPlayer * player, GError * err, GstPlay * play) +error_cb (GstPlaySignalAdapter * adapter, GError * err, Player * play) { gst_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); @@ -83,7 +84,8 @@ error_cb (GstPlayer * player, GError * err, GstPlay * play) } static void -position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) +position_updated_cb (GstPlaySignalAdapter * adapter, GstClockTime pos, + Player * play) { GstClockTime dur = -1; gchar status[64] = { 0, }; @@ -105,13 +107,14 @@ position_updated_cb (GstPlayer * player, GstClockTime pos, GstPlay * play) } static void -state_changed_cb (GstPlayer * player, GstPlayerState state, GstPlay * play) +state_changed_cb (GstPlaySignalAdapter * adapter, GstPlayState state, + Player * play) { - gst_print ("State changed: %s\n", gst_player_state_get_name (state)); + gst_print ("State changed: %s\n", gst_play_state_get_name (state)); } static void -buffering_cb (GstPlayer * player, gint percent, GstPlay * play) +buffering_cb (GstPlaySignalAdapter * adapter, gint percent, Player * play) { gst_print ("Buffering: %d\n", percent); } @@ -148,7 +151,7 @@ print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) } static void -print_video_info (GstPlayerVideoInfo * info) +print_video_info (GstPlayVideoInfo * info) { gint fps_n, fps_d; guint par_n, par_d; @@ -156,159 +159,159 @@ print_video_info (GstPlayerVideoInfo * info) if (info == NULL) return; - gst_print (" width : %d\n", gst_player_video_info_get_width (info)); - gst_print (" height : %d\n", gst_player_video_info_get_height (info)); + gst_print (" width : %d\n", gst_play_video_info_get_width (info)); + gst_print (" height : %d\n", gst_play_video_info_get_height (info)); gst_print (" max_bitrate : %d\n", - gst_player_video_info_get_max_bitrate (info)); - gst_print (" bitrate : %d\n", gst_player_video_info_get_bitrate (info)); - gst_player_video_info_get_framerate (info, &fps_n, &fps_d); + gst_play_video_info_get_max_bitrate (info)); + gst_print (" bitrate : %d\n", gst_play_video_info_get_bitrate (info)); + gst_play_video_info_get_framerate (info, &fps_n, &fps_d); gst_print (" framerate : %.2f\n", (gdouble) fps_n / fps_d); - gst_player_video_info_get_pixel_aspect_ratio (info, &par_n, &par_d); + gst_play_video_info_get_pixel_aspect_ratio (info, &par_n, &par_d); gst_print (" pixel-aspect-ratio %u:%u\n", par_n, par_d); } static void -print_audio_info (GstPlayerAudioInfo * info) +print_audio_info (GstPlayAudioInfo * info) { if (info == NULL) return; gst_print (" sample rate : %d\n", - gst_player_audio_info_get_sample_rate (info)); - gst_print (" channels : %d\n", gst_player_audio_info_get_channels (info)); + gst_play_audio_info_get_sample_rate (info)); + gst_print (" channels : %d\n", gst_play_audio_info_get_channels (info)); gst_print (" max_bitrate : %d\n", - gst_player_audio_info_get_max_bitrate (info)); - gst_print (" bitrate : %d\n", gst_player_audio_info_get_bitrate (info)); - gst_print (" language : %s\n", gst_player_audio_info_get_language (info)); + gst_play_audio_info_get_max_bitrate (info)); + gst_print (" bitrate : %d\n", gst_play_audio_info_get_bitrate (info)); + gst_print (" language : %s\n", gst_play_audio_info_get_language (info)); } static void -print_subtitle_info (GstPlayerSubtitleInfo * info) +print_subtitle_info (GstPlaySubtitleInfo * info) { if (info == NULL) return; - gst_print (" language : %s\n", gst_player_subtitle_info_get_language (info)); + gst_print (" language : %s\n", gst_play_subtitle_info_get_language (info)); } static void -print_all_stream_info (GstPlayerMediaInfo * media_info) +print_all_stream_info (GstPlayMediaInfo * media_info) { guint count = 0; GList *list, *l; - gst_print ("URI : %s\n", gst_player_media_info_get_uri (media_info)); + gst_print ("URI : %s\n", gst_play_media_info_get_uri (media_info)); gst_print ("Duration: %" GST_TIME_FORMAT "\n", - GST_TIME_ARGS (gst_player_media_info_get_duration (media_info))); + GST_TIME_ARGS (gst_play_media_info_get_duration (media_info))); gst_print ("Global taglist:\n"); - if (gst_player_media_info_get_tags (media_info)) - gst_tag_list_foreach (gst_player_media_info_get_tags (media_info), + if (gst_play_media_info_get_tags (media_info)) + gst_tag_list_foreach (gst_play_media_info_get_tags (media_info), print_one_tag, NULL); else gst_print (" (nil) \n"); - list = gst_player_media_info_get_stream_list (media_info); + list = gst_play_media_info_get_stream_list (media_info); if (!list) return; gst_print ("All Stream information\n"); for (l = list; l != NULL; l = l->next) { GstTagList *tags = NULL; - GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; + GstPlayStreamInfo *stream = (GstPlayStreamInfo *) l->data; gst_print (" Stream # %u \n", count++); gst_print (" type : %s_%u\n", - gst_player_stream_info_get_stream_type (stream), - gst_player_stream_info_get_index (stream)); - tags = gst_player_stream_info_get_tags (stream); + gst_play_stream_info_get_stream_type (stream), + gst_play_stream_info_get_index (stream)); + tags = gst_play_stream_info_get_tags (stream); gst_print (" taglist : \n"); if (tags) { gst_tag_list_foreach (tags, print_one_tag, NULL); } - if (GST_IS_PLAYER_VIDEO_INFO (stream)) - print_video_info ((GstPlayerVideoInfo *) stream); - else if (GST_IS_PLAYER_AUDIO_INFO (stream)) - print_audio_info ((GstPlayerAudioInfo *) stream); + if (GST_IS_PLAY_VIDEO_INFO (stream)) + print_video_info ((GstPlayVideoInfo *) stream); + else if (GST_IS_PLAY_AUDIO_INFO (stream)) + print_audio_info ((GstPlayAudioInfo *) stream); else - print_subtitle_info ((GstPlayerSubtitleInfo *) stream); + print_subtitle_info ((GstPlaySubtitleInfo *) stream); } } static void -print_all_video_stream (GstPlayerMediaInfo * media_info) +print_all_video_stream (GstPlayMediaInfo * media_info) { GList *list, *l; - list = gst_player_media_info_get_video_streams (media_info); + list = gst_play_media_info_get_video_streams (media_info); if (!list) return; gst_print ("All video streams\n"); for (l = list; l != NULL; l = l->next) { - GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) l->data; - GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), - gst_player_stream_info_get_index (sinfo)); + GstPlayVideoInfo *info = (GstPlayVideoInfo *) l->data; + GstPlayStreamInfo *sinfo = (GstPlayStreamInfo *) info; + gst_print (" %s_%d #\n", gst_play_stream_info_get_stream_type (sinfo), + gst_play_stream_info_get_index (sinfo)); print_video_info (info); } } static void -print_all_subtitle_stream (GstPlayerMediaInfo * media_info) +print_all_subtitle_stream (GstPlayMediaInfo * media_info) { GList *list, *l; - list = gst_player_media_info_get_subtitle_streams (media_info); + list = gst_play_media_info_get_subtitle_streams (media_info); if (!list) return; gst_print ("All subtitle streams:\n"); for (l = list; l != NULL; l = l->next) { - GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) l->data; - GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), - gst_player_stream_info_get_index (sinfo)); + GstPlaySubtitleInfo *info = (GstPlaySubtitleInfo *) l->data; + GstPlayStreamInfo *sinfo = (GstPlayStreamInfo *) info; + gst_print (" %s_%d #\n", gst_play_stream_info_get_stream_type (sinfo), + gst_play_stream_info_get_index (sinfo)); print_subtitle_info (info); } } static void -print_all_audio_stream (GstPlayerMediaInfo * media_info) +print_all_audio_stream (GstPlayMediaInfo * media_info) { GList *list, *l; - list = gst_player_media_info_get_audio_streams (media_info); + list = gst_play_media_info_get_audio_streams (media_info); if (!list) return; gst_print ("All audio streams: \n"); for (l = list; l != NULL; l = l->next) { - GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) l->data; - GstPlayerStreamInfo *sinfo = (GstPlayerStreamInfo *) info; - gst_print (" %s_%d #\n", gst_player_stream_info_get_stream_type (sinfo), - gst_player_stream_info_get_index (sinfo)); + GstPlayAudioInfo *info = (GstPlayAudioInfo *) l->data; + GstPlayStreamInfo *sinfo = (GstPlayStreamInfo *) info; + gst_print (" %s_%d #\n", gst_play_stream_info_get_stream_type (sinfo), + gst_play_stream_info_get_index (sinfo)); print_audio_info (info); } } static void -print_current_tracks (GstPlay * play) +print_current_tracks (Player * play) { - GstPlayerAudioInfo *audio = NULL; - GstPlayerVideoInfo *video = NULL; - GstPlayerSubtitleInfo *subtitle = NULL; + GstPlayAudioInfo *audio = NULL; + GstPlayVideoInfo *video = NULL; + GstPlaySubtitleInfo *subtitle = NULL; gst_print ("Current video track: \n"); - video = gst_player_get_current_video_track (play->player); + video = gst_play_get_current_video_track (play->player); print_video_info (video); gst_print ("Current audio track: \n"); - audio = gst_player_get_current_audio_track (play->player); + audio = gst_play_get_current_audio_track (play->player); print_audio_info (audio); gst_print ("Current subtitle track: \n"); - subtitle = gst_player_get_current_subtitle_track (play->player); + subtitle = gst_play_get_current_subtitle_track (play->player); print_subtitle_info (subtitle); if (audio) @@ -322,7 +325,7 @@ print_current_tracks (GstPlay * play) } static void -print_media_info (GstPlayerMediaInfo * media_info) +print_media_info (GstPlayMediaInfo * media_info) { print_all_stream_info (media_info); gst_print ("\n"); @@ -334,7 +337,8 @@ print_media_info (GstPlayerMediaInfo * media_info) } static void -media_info_cb (GstPlayer * player, GstPlayerMediaInfo * info, GstPlay * play) +media_info_cb (GstPlaySignalAdapter * adapter, GstPlayMediaInfo * info, + Player * play) { static int once = 0; @@ -345,38 +349,37 @@ media_info_cb (GstPlayer * player, GstPlayerMediaInfo * info, GstPlay * play) } } -static GstPlay * +static Player * play_new (gchar ** uris, gdouble initial_volume) { - GstPlay *play; + Player *play; - play = g_new0 (GstPlay, 1); + play = g_new0 (Player, 1); play->uris = uris; play->num_uris = g_strv_length (uris); play->cur_idx = -1; - play->player = - gst_player_new (NULL); + play->player = gst_play_new (NULL); play->loop = g_main_loop_new (NULL, FALSE); play->desired_state = GST_STATE_PLAYING; - GstPlayerSignalAdapter *player_sig_adapt - = gst_player_signal_adapter_new( - play->player, - g_main_loop_get_context (play->loop)); + play->signal_adapter + = gst_play_signal_adapter_new_with_main_context (play->player, + g_main_loop_get_context (play->loop)); - g_signal_connect (player_sig_adapt, "position-updated", + g_signal_connect (play->signal_adapter, "position-updated", G_CALLBACK (position_updated_cb), play); - g_signal_connect (player_sig_adapt, "state-changed", + g_signal_connect (play->signal_adapter, "state-changed", G_CALLBACK (state_changed_cb), play); - g_signal_connect (player_sig_adapt, "buffering", G_CALLBACK (buffering_cb), play); - g_signal_connect (player_sig_adapt, "end-of-stream", + g_signal_connect (play->signal_adapter, "buffering", + G_CALLBACK (buffering_cb), play); + g_signal_connect (play->signal_adapter, "end-of-stream", G_CALLBACK (end_of_stream_cb), play); - g_signal_connect (player_sig_adapt, "error", G_CALLBACK (error_cb), play); + g_signal_connect (play->signal_adapter, "error", G_CALLBACK (error_cb), play); - g_signal_connect (player_sig_adapt, "media-info-updated", + g_signal_connect (play->signal_adapter, "media-info-updated", G_CALLBACK (media_info_cb), play); play_set_relative_volume (play, initial_volume - 1.0); @@ -385,10 +388,11 @@ play_new (gchar ** uris, gdouble initial_volume) } static void -play_free (GstPlay * play) +play_free (Player * play) { play_reset (play); + g_clear_object (&play->signal_adapter); gst_object_unref (play->player); g_main_loop_unref (play->loop); @@ -399,12 +403,12 @@ play_free (GstPlay * play) /* reset for new file/stream */ static void -play_reset (GstPlay * play) +play_reset (Player * play) { } static void -play_set_relative_volume (GstPlay * play, gdouble volume_step) +play_set_relative_volume (Player * play, gdouble volume_step) { gdouble volume; @@ -418,7 +422,7 @@ play_set_relative_volume (GstPlay * play, gdouble volume_step) } static gchar * -play_uri_get_display_name (GstPlay * play, const gchar * uri) +play_uri_get_display_name (Player * play, const gchar * uri) { gchar *loc; @@ -435,7 +439,7 @@ play_uri_get_display_name (GstPlay * play, const gchar * uri) } static void -play_uri (GstPlay * play, const gchar * next_uri) +play_uri (Player * play, const gchar * next_uri) { gchar *loc; @@ -446,12 +450,12 @@ play_uri (GstPlay * play, const gchar * next_uri) g_free (loc); g_object_set (play->player, "uri", next_uri, NULL); - gst_player_play (play->player); + gst_play_play (play->player); } /* returns FALSE if we have reached the end of the playlist */ static gboolean -play_next (GstPlay * play) +play_next (Player * play) { if ((play->cur_idx + 1) >= play->num_uris) { if (play->repeat) { @@ -467,7 +471,7 @@ play_next (GstPlay * play) /* returns FALSE if we have reached the beginning of the playlist */ static gboolean -play_prev (GstPlay * play) +play_prev (Player * play) { if (play->cur_idx == 0 || play->num_uris <= 1) return FALSE; @@ -477,7 +481,7 @@ play_prev (GstPlay * play) } static void -do_play (GstPlay * play) +do_play (Player * play) { gint i; @@ -550,19 +554,19 @@ restore_terminal (void) } static void -toggle_paused (GstPlay * play) +toggle_paused (Player * play) { if (play->desired_state == GST_STATE_PLAYING) { play->desired_state = GST_STATE_PAUSED; - gst_player_pause (play->player); + gst_play_pause (play->player); } else { play->desired_state = GST_STATE_PLAYING; - gst_player_play (play->player); + gst_play_play (play->player); } } static void -relative_seek (GstPlay * play, gdouble percent) +relative_seek (Player * play, gdouble percent) { gint64 dur = -1, pos = -1; @@ -578,18 +582,18 @@ relative_seek (GstPlay * play, gdouble percent) pos = pos + dur * percent; if (pos < 0) pos = 0; - gst_player_seek (play->player, pos); + gst_play_seek (play->player, pos); } static void keyboard_cb (const gchar * key_input, gpointer user_data) { - GstPlay *play = (GstPlay *) user_data; + Player *play = (Player *) user_data; switch (g_ascii_tolower (key_input[0])) { case 'i': { - GstPlayerMediaInfo *media_info = gst_player_get_media_info (play->player); + GstPlayMediaInfo *media_info = gst_play_get_media_info (play->player); if (media_info) { print_media_info (media_info); g_object_unref (media_info); @@ -640,7 +644,7 @@ keyboard_cb (const gchar * key_input, gpointer user_data) int main (int argc, char **argv) { - GstPlay *play; + Player *play; GPtrArray *playlist; gboolean print_version = FALSE; gboolean interactive = FALSE; /* FIXME: maybe enable by default? */ diff --git a/playback/player/gst-play/meson.build b/playback/player/gst-play/meson.build index 8ec021d0e6..5e80ca8dd4 100644 --- a/playback/player/gst-play/meson.build +++ b/playback/player/gst-play/meson.build @@ -2,5 +2,5 @@ executable('gst-play', ['gst-play.c', 'gst-play-kb.c', 'gst-play-kb.h'], - dependencies : [gst_dep, gstplayer_dep, m_dep]) + dependencies : [gst_dep, dependency('gstreamer-play-1.0'), m_dep]) From e0c77b75cdf20528c0766c1528cc8f0380c45526 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Sun, 6 Dec 2020 12:24:40 +0000 Subject: [PATCH 400/412] gtk-play: Port to GstPlay Part-of: --- playback/player/gtk/gtk-play.c | 277 ++++++++++++----------- playback/player/gtk/gtk-video-renderer.c | 42 ++-- playback/player/gtk/gtk-video-renderer.h | 28 +-- playback/player/gtk/meson.build | 2 +- 4 files changed, 179 insertions(+), 170 deletions(-) diff --git a/playback/player/gtk/gtk-play.c b/playback/player/gtk/gtk-play.c index c6b1b42365..86d357a799 100644 --- a/playback/player/gtk/gtk-play.c +++ b/playback/player/gtk/gtk-play.c @@ -37,7 +37,7 @@ #include -#include +#include #include "gtk-video-renderer.h" #define APP_NAME "gtk-play" @@ -58,8 +58,9 @@ typedef struct { GtkApplicationWindow parent; - GstPlayer *player; - GstPlayerVideoRenderer *renderer; + GstPlay *player; + GstPlaySignalAdapter *signal_adapter; + GstPlayVideoRenderer *renderer; GList *uris; GList *current_uri; @@ -204,11 +205,11 @@ gtk_play_set_rate (GtkPlay * play, gdouble step) { gdouble val; - val = gst_player_get_rate (play->player); + val = gst_play_get_rate (play->player); val += step; if (val == 0.0) val = step; - gst_player_set_rate (play->player, val); + gst_play_set_rate (play->player, val); if (val == 1.0) gtk_label_set_label (play->rate_label, NULL); @@ -302,7 +303,7 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) } case GDK_KEY_BackSpace:{ /* Reset playback speed to normal */ - gdouble val = gst_player_get_rate (play->player); + gdouble val = gst_play_get_rate (play->player); gtk_play_set_rate (play, 1.0 - val); break; } @@ -322,7 +323,7 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) case GDK_KEY_KP_9: case GDK_KEY_9:{ /* Increase volume */ - gdouble volume = gst_player_get_volume (play->player); + gdouble volume = gst_play_get_volume (play->player); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), volume * 1.10); break; @@ -330,15 +331,15 @@ key_press_event_cb (GtkWidget * widget, GdkEventKey * event, gpointer data) case GDK_KEY_KP_0: case GDK_KEY_0:{ /* Decrease volume */ - gdouble volume = gst_player_get_volume (play->player); + gdouble volume = gst_play_get_volume (play->player); gtk_scale_button_set_value (GTK_SCALE_BUTTON (play->volume_button), volume * 0.9); break; } case GDK_KEY_m:{ /* Mute sound */ - gboolean mute = gst_player_get_mute (play->player); - gst_player_set_mute (play->player, !mute); + gboolean mute = gst_play_get_mute (play->player); + gst_play_set_mute (play->player, !mute); break; } case GDK_KEY_f:{ @@ -380,7 +381,7 @@ play_pause_button_clicked_cb (GtkButton * button, GtkPlay * play) GtkWidget *image; if (play->playing) { - gst_player_pause (play->player); + gst_play_pause (play->player); image = TOOLBAR_GET_OBJECT (play_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; @@ -397,7 +398,7 @@ play_pause_button_clicked_cb (GtkButton * button, GtkPlay * play) gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); - gst_player_play (play->player); + gst_play_play (play->player); image = TOOLBAR_GET_OBJECT (pause_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = TRUE; @@ -419,9 +420,9 @@ play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) /* set uri or suburi */ if (ext_suburi) - gst_player_set_subtitle_uri (play->player, ext_suburi); + gst_play_set_subtitle_uri (play->player, ext_suburi); else - gst_player_set_uri (play->player, uri->data); + gst_play_set_uri (play->player, uri->data); play->current_uri = uri; if (play->playing) { if (play->inhibit_cookie) @@ -430,9 +431,9 @@ play_current_uri (GtkPlay * play, GList * uri, const gchar * ext_suburi) play->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play), GTK_APPLICATION_INHIBIT_IDLE, "Playing media"); - gst_player_play (play->player); + gst_play_play (play->player); } else { - gst_player_pause (play->player); + gst_play_pause (play->player); if (play->inhibit_cookie) gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), play->inhibit_cookie); @@ -456,12 +457,12 @@ static gboolean color_balance_channel_change_value_cb (GtkRange * range, GtkScrollType scroll, gdouble value, GtkPlay * play) { - GstPlayerColorBalanceType type; + GstPlayColorBalanceType type; type = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (range), "type")); value = CLAMP (value, 0.0, 1.0); - gst_player_set_color_balance (play->player, type, value); + gst_play_set_color_balance (play->player, type, value); return FALSE; } @@ -470,14 +471,14 @@ static gboolean color_balance_channel_button_press_cb (GtkWidget * widget, GdkEventButton * event, GtkPlay * play) { - GstPlayerColorBalanceType type; + GstPlayColorBalanceType type; if (event->type != GDK_2BUTTON_PRESS) return FALSE; type = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "type")); gtk_range_set_value (GTK_RANGE (widget), 0.5); - gst_player_set_color_balance (play->player, type, 0.5); + gst_play_set_color_balance (play->player, type, 0.5); return FALSE; } @@ -503,10 +504,10 @@ color_balance_dialog (GtkPlay * play) gtk_box_set_homogeneous (GTK_BOX (box), TRUE); gtk_box_pack_start (GTK_BOX (content), box, TRUE, TRUE, 5); - for (i = GST_PLAYER_COLOR_BALANCE_BRIGHTNESS; - i <= GST_PLAYER_COLOR_BALANCE_HUE; i++) { + for (i = GST_PLAY_COLOR_BALANCE_BRIGHTNESS; + i <= GST_PLAY_COLOR_BALANCE_HUE; i++) { ctlbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - label = gtk_label_new (gst_player_color_balance_type_get_name (i)); + label = gtk_label_new (gst_play_color_balance_type_get_name (i)); scale = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, 0, 1, 0.5); gtk_widget_set_size_request (scale, 0, 200); gtk_box_pack_start (GTK_BOX (ctlbox), label, FALSE, TRUE, 2); @@ -514,7 +515,7 @@ color_balance_dialog (GtkPlay * play) gtk_box_pack_end (GTK_BOX (box), ctlbox, TRUE, TRUE, 2); - value = gst_player_get_color_balance (play->player, i); + value = gst_play_get_color_balance (play->player, i); gtk_range_set_value (GTK_RANGE (scale), value); g_object_set_data (G_OBJECT (scale), "type", GUINT_TO_POINTER (i)); @@ -534,7 +535,7 @@ color_balance_dialog (GtkPlay * play) static void color_balance_clicked_cb (GtkWidget * unused, GtkPlay * play) { - if (gst_player_has_color_balance (play->player)) { + if (gst_play_has_color_balance (play->player)) { color_balance_dialog (play); return; } @@ -623,31 +624,31 @@ audio_channels_string (gint num) } static gchar * -stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) +stream_info_get_string (GstPlayStreamInfo * stream, gint type, gboolean label) { gchar *buffer = NULL; switch (type) { case AUDIO_INFO_RATE: { - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; + GstPlayAudioInfo *audio = (GstPlayAudioInfo *) stream; buffer = g_strdup_printf ("%s%d", label ? "Sample rate : " : "", - gst_player_audio_info_get_sample_rate (audio)); + gst_play_audio_info_get_sample_rate (audio)); break; } case AUDIO_INFO_LANGUAGE: { - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; - const gchar *lang = gst_player_audio_info_get_language (audio); + GstPlayAudioInfo *audio = (GstPlayAudioInfo *) stream; + const gchar *lang = gst_play_audio_info_get_language (audio); if (lang) buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", lang); break; } case AUDIO_INFO_CHANNELS: { - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; + GstPlayAudioInfo *audio = (GstPlayAudioInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Channels : " : "", - audio_channels_string (gst_player_audio_info_get_channels (audio))); + audio_channels_string (gst_play_audio_info_get_channels (audio))); break; } case SUBTITLE_INFO_CODEC: @@ -655,13 +656,13 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) case AUDIO_INFO_CODEC: { buffer = g_strdup_printf ("%s%s", label ? "Codec : " : "", - gst_player_stream_info_get_codec (stream)); + gst_play_stream_info_get_codec (stream)); break; } case AUDIO_INFO_MAX_BITRATE: { - GstPlayerAudioInfo *audio = (GstPlayerAudioInfo *) stream; - gint bitrate = gst_player_audio_info_get_max_bitrate (audio); + GstPlayAudioInfo *audio = (GstPlayAudioInfo *) stream; + gint bitrate = gst_play_audio_info_get_max_bitrate (audio); if (bitrate > 0) buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", @@ -670,8 +671,8 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) } case VIDEO_INFO_MAX_BITRATE: { - GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; - gint bitrate = gst_player_video_info_get_max_bitrate (video); + GstPlayVideoInfo *video = (GstPlayVideoInfo *) stream; + gint bitrate = gst_play_video_info_get_max_bitrate (video); if (bitrate > 0) buffer = g_strdup_printf ("%s%d", label ? "Max bitrate : " : "", @@ -681,9 +682,9 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) case VIDEO_INFO_PAR: { guint par_d, par_n; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; + GstPlayVideoInfo *video = (GstPlayVideoInfo *) stream; - gst_player_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); + gst_play_video_info_get_pixel_aspect_ratio (video, &par_n, &par_d); buffer = g_strdup_printf ("%s%u:%u", label ? "pixel-aspect-ratio : " : "", par_n, par_d); break; @@ -691,26 +692,26 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) case VIDEO_INFO_FPS: { gint fps_d, fps_n; - GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; + GstPlayVideoInfo *video = (GstPlayVideoInfo *) stream; - gst_player_video_info_get_framerate (video, &fps_n, &fps_d); + gst_play_video_info_get_framerate (video, &fps_n, &fps_d); buffer = g_strdup_printf ("%s%.2f", label ? "Framerate : " : "", (gdouble) fps_n / fps_d); break; } case VIDEO_INFO_RESOLUTION: { - GstPlayerVideoInfo *video = (GstPlayerVideoInfo *) stream; + GstPlayVideoInfo *video = (GstPlayVideoInfo *) stream; buffer = g_strdup_printf ("%s%dx%d", label ? "Resolution : " : "", - gst_player_video_info_get_width (video), - gst_player_video_info_get_height (video)); + gst_play_video_info_get_width (video), + gst_play_video_info_get_height (video)); break; } case SUBTITLE_INFO_LANGUAGE: { - GstPlayerSubtitleInfo *sub = (GstPlayerSubtitleInfo *) stream; + GstPlaySubtitleInfo *sub = (GstPlaySubtitleInfo *) stream; buffer = g_strdup_printf ("%s%s", label ? "Language : " : "", - gst_player_subtitle_info_get_language (sub)); + gst_play_subtitle_info_get_language (sub)); break; } default: @@ -720,23 +721,23 @@ stream_info_get_string (GstPlayerStreamInfo * stream, gint type, gboolean label) } static void -fill_tree_model (GtkTreeStore * tree, GtkPlay * play, GstPlayerMediaInfo * info) +fill_tree_model (GtkTreeStore * tree, GtkPlay * play, GstPlayMediaInfo * info) { GList *l; guint count; GtkTreeIter child, parent; count = 0; - for (l = gst_player_media_info_get_stream_list (info); l != NULL; l = l->next) { + for (l = gst_play_media_info_get_stream_list (info); l != NULL; l = l->next) { gchar *buffer; gint i, start, end; - GstPlayerStreamInfo *stream = (GstPlayerStreamInfo *) l->data; + GstPlayStreamInfo *stream = (GstPlayStreamInfo *) l->data; /* define the field range based on stream type */ - if (GST_IS_PLAYER_VIDEO_INFO (stream)) { + if (GST_IS_PLAY_VIDEO_INFO (stream)) { start = VIDEO_INFO_START + 1; end = VIDEO_INFO_END; - } else if (GST_IS_PLAYER_AUDIO_INFO (stream)) { + } else if (GST_IS_PLAY_AUDIO_INFO (stream)) { start = AUDIO_INFO_START + 1; end = AUDIO_INFO_END; } else { @@ -750,7 +751,7 @@ fill_tree_model (GtkTreeStore * tree, GtkPlay * play, GstPlayerMediaInfo * info) g_free (buffer); buffer = g_strdup_printf ("Type : %s", - gst_player_stream_info_get_stream_type (stream)); + gst_play_stream_info_get_stream_type (stream)); gtk_tree_store_append (tree, &child, &parent); gtk_tree_store_set (tree, &child, COL_TEXT, buffer, -1); g_free (buffer); @@ -774,7 +775,7 @@ media_info_dialog_button_clicked_cb (GtkButton * button, GtkPlay * play) } static void -media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) +media_info_dialog (GtkPlay * play, GstPlayMediaInfo * media_info) { GtkBuilder *dialog_ui; GtkWidget *view; @@ -814,9 +815,9 @@ media_info_dialog (GtkPlay * play, GstPlayerMediaInfo * media_info) static void media_info_clicked_cb (GtkButton * button, GtkPlay * play) { - GstPlayerMediaInfo *media_info; + GstPlayMediaInfo *media_info; - media_info = gst_player_get_media_info (play->player); + media_info = gst_play_get_media_info (play->player); if (!media_info) return; @@ -891,25 +892,25 @@ G_MODULE_EXPORT void seekbar_value_changed_cb (GtkRange * range, GtkPlay * play) { gdouble value = gtk_range_get_value (GTK_RANGE (play->seekbar)); - gst_player_seek (play->player, gst_util_uint64_scale (value, GST_SECOND, 1)); + gst_play_seek (play->player, gst_util_uint64_scale (value, GST_SECOND, 1)); } G_MODULE_EXPORT void volume_button_value_changed_cb (GtkScaleButton * button, gdouble value, GtkPlay * play) { - gst_player_set_volume (play->player, value); + gst_play_set_volume (play->player, value); } static gint -_get_current_track_index (GtkPlay * play, void *(*func) (GstPlayer * player)) +_get_current_track_index (GtkPlay * play, void *(*func) (GstPlay * player)) { void *obj; gint index = -1; obj = func (play->player); if (obj) { - index = gst_player_stream_info_get_index ((GstPlayerStreamInfo *) obj); + index = gst_play_stream_info_get_index ((GstPlayStreamInfo *) obj); g_object_unref (obj); } @@ -919,21 +920,21 @@ _get_current_track_index (GtkPlay * play, void *(*func) (GstPlayer * player)) static gint get_current_track_index (GtkPlay * play, GType type) { - if (type == GST_TYPE_PLAYER_VIDEO_INFO) + if (type == GST_TYPE_PLAY_VIDEO_INFO) return _get_current_track_index (play, - (void *) gst_player_get_current_video_track); - else if (type == GST_TYPE_PLAYER_AUDIO_INFO) + (void *) gst_play_get_current_video_track); + else if (type == GST_TYPE_PLAY_AUDIO_INFO) return _get_current_track_index (play, - (void *) gst_player_get_current_audio_track); + (void *) gst_play_get_current_audio_track); else return _get_current_track_index (play, - (void *) gst_player_get_current_subtitle_track); + (void *) gst_play_get_current_subtitle_track); } static gchar * -get_menu_label (GstPlayerStreamInfo * stream, GType type) +get_menu_label (GstPlayStreamInfo * stream, GType type) { - if (type == GST_TYPE_PLAYER_AUDIO_INFO) { + if (type == GST_TYPE_PLAY_AUDIO_INFO) { gchar *label = NULL; gchar *lang, *codec, *channels; @@ -953,7 +954,7 @@ get_menu_label (GstPlayerStreamInfo * stream, GType type) g_free (codec); g_free (channels); return label; - } else if (type == GST_TYPE_PLAYER_VIDEO_INFO) { + } else if (type == GST_TYPE_PLAY_VIDEO_INFO) { /* label format: */ return stream_info_get_string (stream, VIDEO_INFO_CODEC, FALSE); } else { @@ -979,26 +980,26 @@ new_subtitle_clicked_cb (GtkWidget * unused, GtkPlay * play) static void disable_track (GtkPlay * play, GType type) { - if (type == GST_TYPE_PLAYER_VIDEO_INFO) { - gst_player_set_video_track_enabled (play->player, FALSE); - } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) - gst_player_set_audio_track_enabled (play->player, FALSE); + if (type == GST_TYPE_PLAY_VIDEO_INFO) { + gst_play_set_video_track_enabled (play->player, FALSE); + } else if (type == GST_TYPE_PLAY_AUDIO_INFO) + gst_play_set_audio_track_enabled (play->player, FALSE); else - gst_player_set_subtitle_track_enabled (play->player, FALSE); + gst_play_set_subtitle_track_enabled (play->player, FALSE); } static void change_track (GtkPlay * play, gint index, GType type) { - if (type == GST_TYPE_PLAYER_VIDEO_INFO) { - gst_player_set_video_track (play->player, index); - gst_player_set_video_track_enabled (play->player, TRUE); - } else if (type == GST_TYPE_PLAYER_AUDIO_INFO) { - gst_player_set_audio_track (play->player, index); - gst_player_set_audio_track_enabled (play->player, TRUE); + if (type == GST_TYPE_PLAY_VIDEO_INFO) { + gst_play_set_video_track (play->player, index); + gst_play_set_video_track_enabled (play->player, TRUE); + } else if (type == GST_TYPE_PLAY_AUDIO_INFO) { + gst_play_set_audio_track (play->player, index); + gst_play_set_audio_track_enabled (play->player, TRUE); } else { - gst_player_set_subtitle_track (play->player, index); - gst_player_set_subtitle_track_enabled (play->player, TRUE); + gst_play_set_subtitle_track (play->player, index); + gst_play_set_subtitle_track_enabled (play->player, TRUE); } } @@ -1029,14 +1030,14 @@ visualization_changed_cb (GtkWidget * widget, GtkPlay * play) if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { name = g_object_get_data (G_OBJECT (widget), "name"); if (g_strcmp0 (name, "disable") == 0) { - gst_player_set_visualization_enabled (play->player, FALSE); + gst_play_set_visualization_enabled (play->player, FALSE); } else { const gchar *vis_name; - gst_player_set_visualization (play->player, name); + gst_play_set_visualization (play->player, name); /* if visualization is not enabled then enable it */ - if (!(vis_name = gst_player_get_current_visualization (play->player))) { - gst_player_set_visualization_enabled (play->player, TRUE); + if (!(vis_name = gst_play_get_current_visualization (play->player))) { + gst_play_set_visualization_enabled (play->player, TRUE); } } } @@ -1050,11 +1051,11 @@ create_visualization_menu (GtkPlay * play) GtkWidget *sep; GSList *group = NULL; const gchar *cur_vis; - GstPlayerVisualization **viss, **p; + GstPlayVisualization **viss, **p; menu = gtk_menu_new (); - cur_vis = gst_player_get_current_visualization (play->player); - viss = gst_player_visualizations_get (); + cur_vis = gst_play_get_current_visualization (play->player); + viss = gst_play_visualizations_get (); p = viss; while (*p) { @@ -1071,7 +1072,7 @@ create_visualization_menu (GtkPlay * play) G_CALLBACK (visualization_changed_cb), play); p++; } - gst_player_visualizations_free (viss); + gst_play_visualizations_free (viss); sep = gtk_separator_menu_item_new (); item = gtk_radio_menu_item_new_with_label (group, "Disable"); @@ -1088,7 +1089,7 @@ create_visualization_menu (GtkPlay * play) } static GtkWidget * -create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) +create_tracks_menu (GtkPlay * play, GstPlayMediaInfo * media_info, GType type) { GtkWidget *menu; GtkWidget *item; @@ -1102,16 +1103,16 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) current_index = get_current_track_index (play, type); - if (type == GST_TYPE_PLAYER_VIDEO_INFO) - list = gst_player_media_info_get_video_streams (media_info); - else if (type == GST_TYPE_PLAYER_AUDIO_INFO) - list = gst_player_media_info_get_audio_streams (media_info); + if (type == GST_TYPE_PLAY_VIDEO_INFO) + list = gst_play_media_info_get_video_streams (media_info); + else if (type == GST_TYPE_PLAY_AUDIO_INFO) + list = gst_play_media_info_get_audio_streams (media_info); else - list = gst_player_media_info_get_subtitle_streams (media_info); + list = gst_play_media_info_get_subtitle_streams (media_info); menu = gtk_menu_new (); - if (type == GST_TYPE_PLAYER_SUBTITLE_INFO) { + if (type == GST_TYPE_PLAY_SUBTITLE_INFO) { GtkWidget *ext_subtitle; ext_subtitle = gtk_menu_item_new_with_label ("New File"); sep = gtk_separator_menu_item_new (); @@ -1124,12 +1125,12 @@ create_tracks_menu (GtkPlay * play, GstPlayerMediaInfo * media_info, GType type) for (l = list; l != NULL; l = l->next) { gint index; gchar *buffer; - GstPlayerStreamInfo *s = (GstPlayerStreamInfo *) l->data; + GstPlayStreamInfo *s = (GstPlayStreamInfo *) l->data; buffer = get_menu_label (s, type); item = gtk_radio_menu_item_new_with_label (group, buffer); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); - index = gst_player_stream_info_get_index (s); + index = gst_play_stream_info_get_index (s); g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (index)); g_object_set_data (G_OBJECT (item), "type", GSIZE_TO_POINTER (type)); if (current_index == index) @@ -1176,7 +1177,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) GtkWidget *submenu; GtkWidget *vis; GtkWidget *cb; - GstPlayerMediaInfo *media_info; + GstPlayMediaInfo *media_info; menu = gtk_menu_new (); info = gtk_menu_item_new_with_label ("Media Information"); @@ -1190,22 +1191,22 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) vis = gtk_menu_item_new_with_label ("Visualization"); cb = gtk_menu_item_new_with_label ("Color Balance"); - media_info = gst_player_get_media_info (play->player); + media_info = gst_play_get_media_info (play->player); - if (media_info && !gst_player_media_info_get_video_streams (media_info)) + if (media_info && !gst_play_media_info_get_video_streams (media_info)) gtk_widget_set_sensitive (video, FALSE); else { - submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_VIDEO_INFO); + submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAY_VIDEO_INFO); if (submenu) gtk_menu_item_set_submenu (GTK_MENU_ITEM (video), submenu); else gtk_widget_set_sensitive (video, FALSE); } - if (media_info && !gst_player_media_info_get_audio_streams (media_info)) + if (media_info && !gst_play_media_info_get_audio_streams (media_info)) gtk_widget_set_sensitive (audio, FALSE); else { - submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAYER_AUDIO_INFO); + submenu = create_tracks_menu (play, media_info, GST_TYPE_PLAY_AUDIO_INFO); if (submenu) gtk_menu_item_set_submenu (GTK_MENU_ITEM (audio), submenu); else @@ -1214,17 +1215,17 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) /* enable visualization menu for audio stream */ if (media_info && - gst_player_media_info_get_audio_streams (media_info) && - !gst_player_media_info_get_video_streams (media_info)) { + gst_play_media_info_get_audio_streams (media_info) && + !gst_play_media_info_get_video_streams (media_info)) { submenu = create_visualization_menu (play); gtk_menu_item_set_submenu (GTK_MENU_ITEM (vis), submenu); } else { gtk_widget_set_sensitive (vis, FALSE); } - if (media_info && gst_player_media_info_get_video_streams (media_info)) { + if (media_info && gst_play_media_info_get_video_streams (media_info)) { submenu = create_tracks_menu (play, media_info, - GST_TYPE_PLAYER_SUBTITLE_INFO); + GST_TYPE_PLAY_SUBTITLE_INFO); gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub), submenu); } else { gtk_widget_set_sensitive (sub, FALSE); @@ -1235,7 +1236,7 @@ gtk_player_popup_menu_create (GtkPlay * play, GdkEventButton * event) gtk_widget_set_sensitive (prev, g_list_previous (play->current_uri) ? TRUE : FALSE); gtk_widget_set_sensitive (info, media_info ? TRUE : FALSE); - gtk_widget_set_sensitive (cb, gst_player_has_color_balance (play->player) ? + gtk_widget_set_sensitive (cb, gst_play_has_color_balance (play->player) ? TRUE : FALSE); g_signal_connect (G_OBJECT (open), "activate", @@ -1424,14 +1425,14 @@ create_ui (GtkPlay * play) gtk_application_add_window (GTK_APPLICATION (g_application_get_default ()), GTK_WINDOW (play)); - play->renderer = gst_player_gtk_video_renderer_new (); + play->renderer = gst_play_gtk_video_renderer_new (); if (play->renderer) { play->video_area = - gst_player_gtk_video_renderer_get_widget (GST_PLAYER_GTK_VIDEO_RENDERER + gst_play_gtk_video_renderer_get_widget (GST_PLAY_GTK_VIDEO_RENDERER (play->renderer)); g_object_unref (play->video_area); } else { - play->renderer = gst_player_video_overlay_video_renderer_new (NULL); + play->renderer = gst_play_video_overlay_video_renderer_new (NULL); play->video_area = gtk_drawing_area_new (); g_signal_connect (play->video_area, "realize", @@ -1510,7 +1511,7 @@ create_ui (GtkPlay * play) } static void -duration_changed_cb (GstPlayer * unused, GstClockTime duration, GtkPlay * play) +duration_changed_cb (GstPlay * unused, GstClockTime duration, GtkPlay * play) { g_signal_handlers_block_by_func (play->seekbar, seekbar_value_changed_cb, play); @@ -1541,22 +1542,26 @@ update_position_label (GtkLabel * label, guint64 seconds) } static void -position_updated_cb (GstPlayer * unused, GstClockTime position, GtkPlay * play) +position_updated_cb (GstPlaySignalAdapter * unused, GstClockTime position, + GtkPlay * play) { + if (!GST_IS_PLAY (play->player)) + return; + g_signal_handlers_block_by_func (play->seekbar, seekbar_value_changed_cb, play); gtk_range_set_value (GTK_RANGE (play->seekbar), (gdouble) position / GST_SECOND); update_position_label (play->elapshed_label, position / GST_SECOND); update_position_label (play->remain_label, - GST_CLOCK_DIFF (position, gst_player_get_duration (play->player)) / + GST_CLOCK_DIFF (position, gst_play_get_duration (play->player)) / GST_SECOND); g_signal_handlers_unblock_by_func (play->seekbar, seekbar_value_changed_cb, play); } static void -eos_cb (GstPlayer * unused, GtkPlay * play) +eos_cb (GstPlaySignalAdapter * unused, GtkPlay * play) { if (play->playing) { GList *next = NULL; @@ -1570,7 +1575,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } else { GtkWidget *image; - gst_player_pause (play->player); + gst_play_pause (play->player); image = TOOLBAR_GET_OBJECT (play_image); gtk_button_set_image (GTK_BUTTON (play->play_pause_button), image); play->playing = FALSE; @@ -1583,7 +1588,7 @@ eos_cb (GstPlayer * unused, GtkPlay * play) } static GdkPixbuf * -gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) +gtk_play_get_cover_image (GstPlayMediaInfo * media_info) { GstSample *sample; GstMapInfo info; @@ -1595,7 +1600,7 @@ gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED; /* get image sample buffer from media */ - sample = gst_player_media_info_get_image_sample (media_info); + sample = gst_play_media_info_get_image_sample (media_info); if (!sample) return NULL; @@ -1640,19 +1645,19 @@ gtk_play_get_cover_image (GstPlayerMediaInfo * media_info) } static void -media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, - GtkPlay * play) +media_info_updated_cb (GstPlaySignalAdapter * adapter, + GstPlayMediaInfo * media_info, GtkPlay * play) { const gchar *title; GdkPixbuf *pixbuf; gchar *basename = NULL; gchar *filename = NULL; - title = gst_player_media_info_get_title (media_info); + title = gst_play_media_info_get_title (media_info); if (!title) { filename = - g_filename_from_uri (gst_player_media_info_get_uri (media_info), NULL, + g_filename_from_uri (gst_play_media_info_get_uri (media_info), NULL, NULL); basename = g_path_get_basename (filename); } @@ -1671,12 +1676,12 @@ media_info_updated_cb (GstPlayer * player, GstPlayerMediaInfo * media_info, } static void -player_volume_changed_cb (GstPlayer * player, GtkPlay * play) +player_volume_changed_cb (GstPlaySignalAdapter * adapter, GtkPlay * play) { gdouble new_val, cur_val; cur_val = gtk_scale_button_get_value (GTK_SCALE_BUTTON (play->volume_button)); - new_val = gst_player_get_volume (play->player); + new_val = gst_play_get_volume (play->player); if (fabs (cur_val - new_val) > 0.001) { g_signal_handlers_block_by_func (play->volume_button, @@ -1742,22 +1747,23 @@ gtk_play_constructor (GType type, guint n_construct_params, create_ui (self); - self->player = - gst_player_new (self->renderer); + self->player = gst_play_new (self->renderer); + self->signal_adapter = gst_play_signal_adapter_new (self->player); - g_signal_connect (self->player, "position-updated", + g_signal_connect (self->signal_adapter, "position-updated", G_CALLBACK (position_updated_cb), self); - g_signal_connect (self->player, "duration-changed", + g_signal_connect (self->signal_adapter, "duration-changed", G_CALLBACK (duration_changed_cb), self); - g_signal_connect (self->player, "end-of-stream", G_CALLBACK (eos_cb), self); - g_signal_connect (self->player, "media-info-updated", + g_signal_connect (self->signal_adapter, "end-of-stream", G_CALLBACK (eos_cb), + self); + g_signal_connect (self->signal_adapter, "media-info-updated", G_CALLBACK (media_info_updated_cb), self); - g_signal_connect (self->player, "volume-changed", + g_signal_connect (self->signal_adapter, "volume-changed", G_CALLBACK (player_volume_changed_cb), self); /* enable visualization (by default playbin uses goom) */ /* if visualization is enabled then use the first element */ - gst_player_set_visualization_enabled (self->player, TRUE); + gst_play_set_visualization_enabled (self->player, TRUE); g_signal_connect (G_OBJECT (self), "show", G_CALLBACK (show_cb), NULL); @@ -1777,9 +1783,12 @@ gtk_play_dispose (GObject * object) if (self->uris) g_list_free_full (self->uris, g_free); self->uris = NULL; + + g_clear_object (&self->signal_adapter); + if (self->player) { - gst_player_stop (self->player); - g_object_unref (self->player); + gst_play_stop (self->player); + gst_object_unref (self->player); } self->player = NULL; diff --git a/playback/player/gtk/gtk-video-renderer.c b/playback/player/gtk/gtk-video-renderer.c index f07d82403c..92d9a9a7ff 100644 --- a/playback/player/gtk/gtk-video-renderer.c +++ b/playback/player/gtk/gtk-video-renderer.c @@ -20,7 +20,7 @@ #include "gtk-video-renderer.h" -struct _GstPlayerGtkVideoRenderer +struct _GstPlayGtkVideoRenderer { GObject parent; @@ -28,14 +28,14 @@ struct _GstPlayerGtkVideoRenderer GtkWidget *widget; }; -struct _GstPlayerGtkVideoRendererClass +struct _GstPlayGtkVideoRendererClass { GObjectClass parent_class; }; static void gst_player_gtk_video_renderer_interface_init - (GstPlayerVideoRendererInterface * iface); + (GstPlayVideoRendererInterface * iface); enum { @@ -44,9 +44,9 @@ enum GTK_VIDEO_RENDERER_PROP_LAST }; -G_DEFINE_TYPE_WITH_CODE (GstPlayerGtkVideoRenderer, +G_DEFINE_TYPE_WITH_CODE (GstPlayGtkVideoRenderer, gst_player_gtk_video_renderer, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAY_VIDEO_RENDERER, gst_player_gtk_video_renderer_interface_init)); static GParamSpec @@ -56,7 +56,7 @@ static void gst_player_gtk_video_renderer_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { - GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (object); + GstPlayGtkVideoRenderer *self = GST_PLAY_GTK_VIDEO_RENDERER (object); switch (prop_id) { case GTK_VIDEO_RENDERER_PROP_WIDGET: @@ -71,7 +71,7 @@ gst_player_gtk_video_renderer_get_property (GObject * object, static void gst_player_gtk_video_renderer_finalize (GObject * object) { - GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (object); + GstPlayGtkVideoRenderer *self = GST_PLAY_GTK_VIDEO_RENDERER (object); if (self->sink) gst_object_unref (self->sink); @@ -84,7 +84,7 @@ gst_player_gtk_video_renderer_finalize (GObject * object) static void gst_player_gtk_video_renderer_class_init - (GstPlayerGtkVideoRendererClass * klass) + (GstPlayGtkVideoRendererClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); @@ -102,7 +102,7 @@ static void } static void -gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) +gst_player_gtk_video_renderer_init (GstPlayGtkVideoRenderer * self) { GstElement *gtk_sink = gst_element_factory_make ("gtkglsink", NULL); @@ -122,27 +122,27 @@ gst_player_gtk_video_renderer_init (GstPlayerGtkVideoRenderer * self) } static GstElement *gst_player_gtk_video_renderer_create_video_sink - (GstPlayerVideoRenderer * iface, GstPlayer * player) + (GstPlayVideoRenderer * iface, GstPlay * player) { - GstPlayerGtkVideoRenderer *self = GST_PLAYER_GTK_VIDEO_RENDERER (iface); + GstPlayGtkVideoRenderer *self = GST_PLAY_GTK_VIDEO_RENDERER (iface); return gst_object_ref (self->sink); } static void gst_player_gtk_video_renderer_interface_init - (GstPlayerVideoRendererInterface * iface) + (GstPlayVideoRendererInterface * iface) { iface->create_video_sink = gst_player_gtk_video_renderer_create_video_sink; } /** - * gst_player_gtk_video_renderer_new: + * gst_play_gtk_video_renderer_new: * * Returns: (transfer full): */ -GstPlayerVideoRenderer * -gst_player_gtk_video_renderer_new (void) +GstPlayVideoRenderer * +gst_play_gtk_video_renderer_new (void) { GstElementFactory *factory; @@ -154,21 +154,21 @@ gst_player_gtk_video_renderer_new (void) gst_object_unref (factory); - return g_object_new (GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, NULL); + return g_object_new (GST_TYPE_PLAY_GTK_VIDEO_RENDERER, NULL); } /** - * gst_player_gtk_video_renderer_get_widget: - * @self: #GstPlayerVideoRenderer instance + * gst_play_gtk_video_renderer_get_widget: + * @self: #GstPlayVideoRenderer instance * * Returns: (transfer full): The GtkWidget */ -GtkWidget *gst_player_gtk_video_renderer_get_widget - (GstPlayerGtkVideoRenderer * self) +GtkWidget *gst_play_gtk_video_renderer_get_widget + (GstPlayGtkVideoRenderer * self) { GtkWidget *widget; - g_return_val_if_fail (GST_IS_PLAYER_GTK_VIDEO_RENDERER (self), NULL); + g_return_val_if_fail (GST_IS_PLAY_GTK_VIDEO_RENDERER (self), NULL); g_object_get (self, "widget", &widget, NULL); diff --git a/playback/player/gtk/gtk-video-renderer.h b/playback/player/gtk/gtk-video-renderer.h index 482b609eb1..91deeff4c0 100644 --- a/playback/player/gtk/gtk-video-renderer.h +++ b/playback/player/gtk/gtk-video-renderer.h @@ -21,28 +21,28 @@ #ifndef __GTK_VIDEO_RENDERER_H__ #define __GTK_VIDEO_RENDERER_H__ -#include +#include #include G_BEGIN_DECLS -typedef struct _GstPlayerGtkVideoRenderer - GstPlayerGtkVideoRenderer; -typedef struct _GstPlayerGtkVideoRendererClass - GstPlayerGtkVideoRendererClass; +typedef struct _GstPlayGtkVideoRenderer + GstPlayGtkVideoRenderer; +typedef struct _GstPlayGtkVideoRendererClass + GstPlayGtkVideoRendererClass; -#define GST_TYPE_PLAYER_GTK_VIDEO_RENDERER (gst_player_gtk_video_renderer_get_type ()) -#define GST_IS_PLAYER_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER)) -#define GST_IS_PLAYER_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER)) -#define GST_PLAYER_GTK_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRendererClass)) -#define GST_PLAYER_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRenderer)) -#define GST_PLAYER_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_GTK_VIDEO_RENDERER, GstPlayerGtkVideoRendererClass)) -#define GST_PLAYER_GTK_VIDEO_RENDERER_CAST(obj) ((GstPlayerGtkVideoRenderer*)(obj)) +#define GST_TYPE_PLAY_GTK_VIDEO_RENDERER (gst_player_gtk_video_renderer_get_type ()) +#define GST_IS_PLAY_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_GTK_VIDEO_RENDERER)) +#define GST_IS_PLAY_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_GTK_VIDEO_RENDERER)) +#define GST_PLAY_GTK_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAY_GTK_VIDEO_RENDERER, GstPlayGtkVideoRendererClass)) +#define GST_PLAY_GTK_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_GTK_VIDEO_RENDERER, GstPlayGtkVideoRenderer)) +#define GST_PLAY_GTK_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_GTK_VIDEO_RENDERER, GstPlayGtkVideoRendererClass)) +#define GST_PLAY_GTK_VIDEO_RENDERER_CAST(obj) ((GstPlayGtkVideoRenderer*)(obj)) GType gst_player_gtk_video_renderer_get_type (void); -GstPlayerVideoRenderer * gst_player_gtk_video_renderer_new (void); -GtkWidget * gst_player_gtk_video_renderer_get_widget (GstPlayerGtkVideoRenderer * self); +GstPlayVideoRenderer * gst_play_gtk_video_renderer_new (void); +GtkWidget * gst_play_gtk_video_renderer_get_widget (GstPlayGtkVideoRenderer * self); G_END_DECLS diff --git a/playback/player/gtk/meson.build b/playback/player/gtk/meson.build index f7a741966e..a09744f538 100644 --- a/playback/player/gtk/meson.build +++ b/playback/player/gtk/meson.build @@ -13,5 +13,5 @@ if gtk_dep.found() gtk_play_resources, 'gtk-video-renderer.h', 'gtk-video-renderer.c'], - dependencies : [glib_dep, gobject_dep, gmodule_dep, gst_dep, gsttag_dep, gstplayer_dep, gtk_dep, x11_dep]) + dependencies : [glib_dep, gobject_dep, gmodule_dep, gst_dep, gsttag_dep, gstplay_dep, gtk_dep, x11_dep]) endif From 2c3d78c9a6b6da354a179e99b63069f6d2bdb204 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Wed, 10 Mar 2021 09:13:27 +0530 Subject: [PATCH 401/412] webrtc/signalling: Document cert exception needed for browsers Fixes https://gitlab.freedesktop.org/gstreamer/gst-examples/-/issues/28 Part-of: --- webrtc/signalling/README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/webrtc/signalling/README.md b/webrtc/signalling/README.md index e17e9380d5..e07ee3a930 100644 --- a/webrtc/signalling/README.md +++ b/webrtc/signalling/README.md @@ -9,15 +9,23 @@ Read Protocol.md ## Example usage -In three separate tabs, run consecutively: +For dev usage, generate a self-signed certificate and run the server: ```console $ ./generate_cert.sh $ ./simple_server.py ``` +If you want to use this server from the browser (to use the JS example, for +instance), you will need to go to `https://127.0.0.1:8443` and accept the +self-signed certificate. This step is not required if you will be deploying on +a server with a CA-signed certificate, in which case you should use +`./simple_server.py --cert-path `. + ### Session Based +In two new consoles, run these two commands: + ```console $ ./session-client.py Our uid is 'ws-test-client-8f63b9' @@ -28,14 +36,14 @@ $ ./session-client.py --call ws-test-client-8f63b9 ``` ### Room Based +Or, if you want to test rooms, run these two in two new consoles: + ```console $ ./room-client.py --room 123 Our uid is 'ws-test-client-bdb5b9' Got ROOM_OK for room '123' ``` -Another window - ```console $ ./room-client.py --room 123 Our uid is 'ws-test-client-78b59a' @@ -45,4 +53,4 @@ Sent: ROOM_PEER_MSG ws-test-client-bdb5b9 {"sdp": "initial sdp"} Got answer from 'ws-test-client-bdb5b9': {"sdp": "reply sdp"} ``` -.. and similar output with more clients in the same room. +You will see similar output with more clients in the same room. From c89dccbf4e06046c2eddd10eff7ea40fa5317392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Fri, 30 Apr 2021 17:19:05 -0400 Subject: [PATCH 402/412] validate README: Document paths for gst-examples As the webrtc demos have now been merged, change the paths for easier copy-pasting. Part-of: --- webrtc/check/validate/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/check/validate/README.md b/webrtc/check/validate/README.md index ea071532cc..35a775d4ba 100644 --- a/webrtc/check/validate/README.md +++ b/webrtc/check/validate/README.md @@ -14,4 +14,4 @@ testing framework. # Run the tests -`GST_VALIDATE_APPS_DIR=/path/to/gstwebrtc-demos/check/validate/apps/ GST_VALIDATE_SCENARIOS_PATH=/path/to/gstwebrtc-demos/check/validate/scenarios/ gst-validate-launcher --testsuites-dir /path/to/gstwebrtc-demos/check/validate/testsuites/ webrtc` +`GST_VALIDATE_APPS_DIR=/path/to/gst-examples/webrtc/check/validate/apps/ GST_VALIDATE_SCENARIOS_PATH=/path/to/gst-examples/webrtc/check/validate/scenarios/ gst-validate-launcher --testsuites-dir /path/to/gst-examples/webrtc/check/validate/testsuites/ webrtc` From 875e01e90ae04d2b6e389909e3e8ce79c1a410a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Tue, 20 Apr 2021 22:35:52 +0200 Subject: [PATCH 403/412] Use gst_element_request_pad_simple... Instead of the deprecated gst_element_get_request_pad. Part-of: --- webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c index 5f8076eeb5..5047510151 100644 --- a/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c +++ b/webrtc/multiparty-sendrecv/gst/mp-webrtc-sendrecv.c @@ -346,7 +346,7 @@ add_peer_to_pipeline (const gchar * peer_id, gboolean offer) srcpad = gst_element_get_static_pad (q, "src"); g_assert_nonnull (srcpad); - sinkpad = gst_element_get_request_pad (webrtc, "sink_%u"); + sinkpad = gst_element_request_pad_simple (webrtc, "sink_%u"); g_assert_nonnull (sinkpad); ret = gst_pad_link (srcpad, sinkpad); g_assert_cmpint (ret, ==, GST_PAD_LINK_OK); @@ -355,7 +355,7 @@ add_peer_to_pipeline (const gchar * peer_id, gboolean offer) tee = gst_bin_get_by_name (GST_BIN (pipeline), "audiotee"); g_assert_nonnull (tee); - srcpad = gst_element_get_request_pad (tee, "src_%u"); + srcpad = gst_element_request_pad_simple (tee, "src_%u"); g_assert_nonnull (srcpad); gst_object_unref (tee); sinkpad = gst_element_get_static_pad (q, "sink"); From 5f9ba620ce9e0b91397636c13e0bd1153cbcf348 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 7 May 2021 14:19:43 +1000 Subject: [PATCH 404/412] webrtc/validate: update for fixed data channel closing scenario Requires: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2186 Part-of: --- webrtc/check/validate/scenarios/open_data_channel.scenario | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/check/validate/scenarios/open_data_channel.scenario b/webrtc/check/validate/scenarios/open_data_channel.scenario index 8c1bc391b1..47eade6ec4 100644 --- a/webrtc/check/validate/scenarios/open_data_channel.scenario +++ b/webrtc/check/validate/scenarios/open_data_channel.scenario @@ -18,6 +18,6 @@ wait-for-data-channel, which="$(negotiation_responder)", id="gstreamer"; wait-for-data-channel, which="$(negotiation_initiator)", id="gstreamer"; wait-for-data-channel-state, which="$(negotiation_initiator)", id="gstreamer", state="open"; -# only the browser closing works at the moment -close-data-channel, which="remote", id="gstreamer" -wait-for-data-channel-state, which="local", id="gstreamer", state="closed"; +# close the data channel +close-data-channel, which="$(negotiation_initiator)", id="gstreamer" +wait-for-data-channel-state, which="$(negotiation_responder)", id="gstreamer", state="closed"; From ba079092f8a6e3fcd653065b1eb80c89e4f35eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 21 Apr 2021 16:27:38 -0400 Subject: [PATCH 405/412] webrtc: Use properties to access the inside of the transceiver object This will allow hiding the insides from unsafe application access. Part-of: --- webrtc/sendonly/webrtc-unidirectional-h264.c | 24 +++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/webrtc/sendonly/webrtc-unidirectional-h264.c b/webrtc/sendonly/webrtc-unidirectional-h264.c index 48fe8a05c2..593d86141a 100644 --- a/webrtc/sendonly/webrtc-unidirectional-h264.c +++ b/webrtc/sendonly/webrtc-unidirectional-h264.c @@ -259,22 +259,34 @@ create_receiver_entry (SoupWebsocketConnection * connection) &transceivers); g_assert (transceivers != NULL && transceivers->len > 1); trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); - trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + g_object_set (trans, "direction", + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, NULL); if (video_priority) { GstWebRTCPriorityType priority; priority = _priority_from_string (video_priority); - if (priority) - gst_webrtc_rtp_sender_set_priority (trans->sender, priority); + if (priority) { + GstWebRTCRTPSender *sender; + + g_object_get (trans, "sender", &sender, NULL); + gst_webrtc_rtp_sender_set_priority (sender, priority); + g_object_unref (sender); + } } trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1); - trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + g_object_set (trans, "direction", + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, NULL); if (audio_priority) { GstWebRTCPriorityType priority; priority = _priority_from_string (audio_priority); - if (priority) - gst_webrtc_rtp_sender_set_priority (trans->sender, priority); + if (priority) { + GstWebRTCRTPSender *sender; + + g_object_get (trans, "sender", &sender, NULL); + gst_webrtc_rtp_sender_set_priority (sender, priority); + g_object_unref (sender); + } } g_array_unref (transceivers); From a028a4cb84d2c3591f422d6e0db42a6feb2bcda5 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 30 Apr 2021 14:15:01 +1000 Subject: [PATCH 406/412] webrtc/sendrecv/c: add twcc by default Part-of: --- webrtc/sendrecv/gst/webrtc-sendrecv.c | 96 ++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/webrtc/sendrecv/gst/webrtc-sendrecv.c b/webrtc/sendrecv/gst/webrtc-sendrecv.c index 709ac70102..0ab61c7a86 100644 --- a/webrtc/sendrecv/gst/webrtc-sendrecv.c +++ b/webrtc/sendrecv/gst/webrtc-sendrecv.c @@ -8,6 +8,7 @@ */ #include #include +#include #define GST_USE_UNSTABLE_API #include @@ -39,6 +40,9 @@ enum AppState PEER_CALL_ERROR, }; +#define GST_CAT_DEFAULT webrtc_sendrecv_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + static GMainLoop *loop; static GstElement *pipe1, *webrtc1 = NULL; static GObject *send_channel, *receive_channel; @@ -372,6 +376,53 @@ on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec, gst_print ("ICE gathering state changed to %s\n", new_state); } +static gboolean webrtcbin_get_stats (GstElement * webrtcbin); + +static gboolean +on_webrtcbin_stat (GQuark field_id, const GValue * value, gpointer unused) +{ + if (GST_VALUE_HOLDS_STRUCTURE (value)) { + GST_DEBUG ("stat: \'%s\': %" GST_PTR_FORMAT, g_quark_to_string (field_id), + gst_value_get_structure (value)); + } else { + GST_FIXME ("unknown field \'%s\' value type: \'%s\'", + g_quark_to_string (field_id), g_type_name (G_VALUE_TYPE (value))); + } + + return TRUE; +} + +static void +on_webrtcbin_get_stats (GstPromise * promise, GstElement * webrtcbin) +{ + const GstStructure *stats; + + g_return_if_fail (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + + stats = gst_promise_get_reply (promise); + gst_structure_foreach (stats, on_webrtcbin_stat, NULL); + + g_timeout_add (100, (GSourceFunc) webrtcbin_get_stats, webrtcbin); +} + +static gboolean +webrtcbin_get_stats (GstElement * webrtcbin) +{ + GstPromise *promise; + + promise = + gst_promise_new_with_change_func ( + (GstPromiseChangeFunc) on_webrtcbin_get_stats, webrtcbin, NULL); + + GST_TRACE ("emitting get-stats on %" GST_PTR_FORMAT, webrtcbin); + g_signal_emit_by_name (webrtcbin, "get-stats", NULL, promise); + gst_promise_unref (promise); + + return G_SOURCE_REMOVE; +} + +#define RTP_TWCC_URI "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + static gboolean start_pipeline (gboolean create_offer) { @@ -381,9 +432,16 @@ start_pipeline (gboolean create_offer) pipe1 = gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER - "videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " + "videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! " + /* increase the default keyframe distance, browsers have really long + * periods between keyframes and rely on PLI events on packet loss to + * fix corrupted video. + */ + "vp8enc deadline=1 keyframe-max-dist=2000 ! " + /* picture-id-mode=15-bit seems to make TWCC stats behave better */ + "rtpvp8pay name=videopay picture-id-mode=15-bit ! " "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " - "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " + "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay name=audiopay ! " "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", &error); if (error) { @@ -395,6 +453,35 @@ start_pipeline (gboolean create_offer) webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "sendrecv"); g_assert_nonnull (webrtc1); + if (remote_is_offerer) { + /* XXX: this will fail when the remote offers twcc as the extension id + * cannot currently be negotiated when receiving an offer. + */ + GST_FIXME ("Need to implement header extension negotiation when " + "reciving a remote offers"); + } else { + GstElement *videopay, *audiopay; + GstRTPHeaderExtension *video_twcc, *audio_twcc; + + videopay = gst_bin_get_by_name (GST_BIN (pipe1), "videopay"); + g_assert_nonnull (videopay); + video_twcc = gst_rtp_header_extension_create_from_uri (RTP_TWCC_URI); + g_assert_nonnull (video_twcc); + gst_rtp_header_extension_set_id (video_twcc, 1); + g_signal_emit_by_name (videopay, "add-extension", video_twcc); + g_clear_object (&video_twcc); + g_clear_object (&videopay); + + audiopay = gst_bin_get_by_name (GST_BIN (pipe1), "audiopay"); + g_assert_nonnull (audiopay); + audio_twcc = gst_rtp_header_extension_create_from_uri (RTP_TWCC_URI); + g_assert_nonnull (audio_twcc); + gst_rtp_header_extension_set_id (audio_twcc, 1); + g_signal_emit_by_name (audiopay, "add-extension", audio_twcc); + g_clear_object (&audio_twcc); + g_clear_object (&audiopay); + } + /* This is the gstwebrtc entry point where we create the offer and so on. It * will be called when the pipeline goes to PLAYING. */ g_signal_connect (webrtc1, "on-negotiation-needed", @@ -426,6 +513,8 @@ start_pipeline (gboolean create_offer) /* Lifetime is the same as the pipeline itself */ gst_object_unref (webrtc1); + g_timeout_add (100, (GSourceFunc) webrtcbin_get_stats, webrtc1); + gst_print ("Starting pipeline\n"); ret = gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) @@ -832,6 +921,9 @@ main (int argc, char *argv[]) return -1; } + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "webrtc-sendrecv", 0, + "WebRTC Sending and Receiving example"); + if (!check_plugins ()) { goto out; } From a3c662b2e3210dd34f954f04f924c434e5b5aca2 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 25 May 2021 00:32:24 +0900 Subject: [PATCH 407/412] meson: Fix build error caused by missing rtp dep Missing RTP dep causes build error on Windows webrtc-sendrecv.c.obj : error LNK2019: unresolved external symbol __imp_gst_rtp_header_extension_set_id referenced in function start_pipeline webrtc-sendrecv.c.obj : error LNK2019: unresolved external symbol __imp_gst_rtp_header_extension_create_from_uri referenced in function start_pipeline ... and match required GStreamer to gst-example project version Part-of: --- meson.build | 22 ++++++++++++++++------ webrtc/sendrecv/gst/meson.build | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index 3f0f994d7f..7d0cab3571 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,14 @@ if cc.get_id() == 'msvc' language : 'c') endif +gst_version = meson.project_version() +version_arr = gst_version.split('.') + +gst_version_major = version_arr[0].to_int() +gst_version_minor = version_arr[1].to_int() + +gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor) + glib_dep = dependency('glib-2.0', version: '>= 2.38', fallback: ['glib', 'libglib_dep']) gio_dep = dependency('gio-2.0', @@ -18,16 +26,18 @@ gobject_dep = dependency('gobject-2.0', version: '>= 2.38', gmodule_dep = dependency('gmodule-2.0', fallback: ['glib', 'libgmodule_dep']) -gst_dep = dependency('gstreamer-1.0', +gst_dep = dependency('gstreamer-1.0', version: gst_req, fallback : ['gstreamer', 'gst_dep']) -gstplay_dep = dependency('gstreamer-play-1.0', version: '>= 1.19.0.1', +gstplay_dep = dependency('gstreamer-play-1.0', version: gst_req, fallback: ['gst-plugins-bad', 'gstplay_dep']) -gsttag_dep = dependency('gstreamer-tag-1.0', +gsttag_dep = dependency('gstreamer-tag-1.0', version: gst_req, fallback: ['gst-plugins-base', 'tag_dep']) -gstwebrtc_dep = dependency('gstreamer-webrtc-1.0', version: '>= 1.14.0', +gstwebrtc_dep = dependency('gstreamer-webrtc-1.0', version: gst_req, fallback: ['gst-plugins-bad', 'gstwebrtc_dep']) -gstsdp_dep = dependency('gstreamer-webrtc-1.0', - fallback : ['gst-plugins-bad', 'gstwebrtc_dep']) +gstsdp_dep = dependency('gstreamer-sdp-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'sdp_dep']) +gstrtp_dep = dependency('gstreamer-rtp-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'rtp_dep']) subdir('playback') subdir('network') diff --git a/webrtc/sendrecv/gst/meson.build b/webrtc/sendrecv/gst/meson.build index 5c9509c7e1..1ed55e558e 100644 --- a/webrtc/sendrecv/gst/meson.build +++ b/webrtc/sendrecv/gst/meson.build @@ -1,5 +1,5 @@ executable('webrtc-sendrecv', 'webrtc-sendrecv.c', - dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, libsoup_dep, json_glib_dep ]) + dependencies : [gst_dep, gstsdp_dep, gstwebrtc_dep, gstrtp_dep, libsoup_dep, json_glib_dep]) webrtc_py = files('webrtc_sendrecv.py') From 778464f1388fcd07d8092f9abfded996267434b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 1 Jun 2021 00:17:01 +0100 Subject: [PATCH 408/412] Release 1.19.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7d0cab3571..4759669e9d 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.19.0.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.19.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From fac3ca0a93c432980b0eab3a4ea5a6dfe8e167b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 1 Jun 2021 15:29:23 +0100 Subject: [PATCH 409/412] Back to development --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4759669e9d..3c6db26d12 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.19.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.19.1.1', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) From f5cbbab4d5af1fd303c070f9ffd19eba025e47b5 Mon Sep 17 00:00:00 2001 From: Corey C Date: Mon, 21 Jun 2021 22:20:40 +0000 Subject: [PATCH 410/412] fix(gst-rust): response spelling typo Part-of: --- webrtc/sendrecv/gst-rust/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webrtc/sendrecv/gst-rust/src/main.rs b/webrtc/sendrecv/gst-rust/src/main.rs index 904b0727f4..3e457a3020 100644 --- a/webrtc/sendrecv/gst-rust/src/main.rs +++ b/webrtc/sendrecv/gst-rust/src/main.rs @@ -305,10 +305,10 @@ impl App { let reply = match reply { Ok(Some(reply)) => reply, Ok(None) => { - bail!("Offer creation future got no reponse"); + bail!("Offer creation future got no response"); } Err(err) => { - bail!("Offer creation future got error reponse: {:?}", err); + bail!("Offer creation future got error response: {:?}", err); } }; @@ -351,10 +351,10 @@ impl App { let reply = match reply { Ok(Some(reply)) => reply, Ok(None) => { - bail!("Answer creation future got no reponse"); + bail!("Answer creation future got no response"); } Err(err) => { - bail!("Answer creation future got error reponse: {:?}", err); + bail!("Answer creation future got error response: {:?}", err); } }; From 5c4cc517f00dffe5f6f7af8e8719be5a1eab9060 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Fri, 6 Aug 2021 15:49:13 +1000 Subject: [PATCH 411/412] webrtc/janus/rust: update to latest GStreamer rust bindings Part-of: --- webrtc/janus/rust/Cargo.lock | 601 +++++++++++++++++---------------- webrtc/janus/rust/Cargo.toml | 12 +- webrtc/janus/rust/src/janus.rs | 71 ++-- webrtc/janus/rust/src/main.rs | 15 +- 4 files changed, 354 insertions(+), 345 deletions(-) diff --git a/webrtc/janus/rust/Cargo.lock b/webrtc/janus/rust/Cargo.lock index 0ae8b18a83..95da72ad62 100644 --- a/webrtc/janus/rust/Cargo.lock +++ b/webrtc/janus/rust/Cargo.lock @@ -1,32 +1,34 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.32" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "async-tungstenite" -version = "0.8.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" +checksum = "8645e929ec7964448a901db9da30cd2ae8c7fecf4d6176af427837531dbbb63b" dependencies = [ "futures-io", "futures-util", "gio", "glib", "log", - "pin-project", + "pin-project-lite", "tungstenite", ] @@ -43,15 +45,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" @@ -61,54 +63,45 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", "generic-array", ] -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "0.5.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "cfg-expr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a1d12766bbdd5d44caab5df04a9bffec9cd855a1b44b15de5665d70c085f94" +dependencies = [ + "smallvec", +] [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.1" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "bitflags", "textwrap", @@ -116,19 +109,28 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.8.1" +name = "cpufeatures" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ "generic-array", ] [[package]] name = "either" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" @@ -143,12 +145,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fnv" version = "1.0.7" @@ -156,10 +152,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "futures" -version = "0.3.5" +name = "form_urlencoded" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" dependencies = [ "futures-channel", "futures-core", @@ -172,9 +178,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" dependencies = [ "futures-core", "futures-sink", @@ -182,15 +188,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" [[package]] name = "futures-executor" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" dependencies = [ "futures-core", "futures-task", @@ -199,16 +205,17 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" [[package]] name = "futures-macro" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" dependencies = [ + "autocfg", "proc-macro-hack", "proc-macro2", "quote", @@ -217,25 +224,23 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" +checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" [[package]] name = "futures-task" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" -dependencies = [ - "once_cell", -] +checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2" [[package]] name = "futures-util" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78" dependencies = [ + "autocfg", "futures-channel", "futures-core", "futures-io", @@ -243,7 +248,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -252,40 +257,48 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", + "version_check", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] name = "gio" -version = "0.9.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5492e80b45e6c56214894a9a0cbe1340ab5066eb44a2dbe151393b6d7942c0" +checksum = "86c6823b39d46d22cac2466de261f28d7f049ebc18f7b35296a42c7ed8a88325" dependencies = [ "bitflags", - "futures", "futures-channel", "futures-core", "futures-io", - "futures-util", "gio-sys", "glib", - "glib-sys", - "gobject-sys", "libc", "once_cell", "thiserror", @@ -293,44 +306,44 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35993626299fbcaa73c0a19be8fdd01c950f9f3d3ac9cb4fb5532b924ab1a5d7" +checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", + "winapi", ] [[package]] name = "glib" -version = "0.10.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e0533f48640d86e8e2f3cee778a9f97588d4a0bec8be065ee51ea52346d6c1" +checksum = "dbecad7a3a898ee749d491ce2ae0decb0bce9e736f9747bc49159b1cea5d37f4" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", - "futures-util", "glib-macros", "glib-sys", "gobject-sys", "libc", "once_cell", + "smallvec", ] [[package]] name = "glib-macros" -version = "0.10.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" +checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" dependencies = [ "anyhow", "heck", - "itertools", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -340,9 +353,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" +checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" dependencies = [ "libc", "system-deps", @@ -350,9 +363,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" +checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" dependencies = [ "glib-sys", "libc", @@ -361,9 +374,9 @@ dependencies = [ [[package]] name = "gstreamer" -version = "0.16.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce4ce1ba28d3293b8cb8c3d33f50e6da2e5cfeefa59a0d10d922ab8015791609" +checksum = "1f71e8c163945a3e0140e6b1ed10d1507da7e71196fedec568cbad92db63d762" dependencies = [ "bitflags", "cfg-if", @@ -371,11 +384,10 @@ dependencies = [ "futures-core", "futures-util", "glib", - "glib-sys", - "gobject-sys", "gstreamer-sys", "libc", "muldiv", + "num-integer", "num-rational", "once_cell", "paste", @@ -385,26 +397,22 @@ dependencies = [ [[package]] name = "gstreamer-sdp" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebbe4fbea4f2c67982c667ba57b64b01d43bc3dc1dc4a8d3512cb33a2adc7c1" +checksum = "4fb915df42663a982c64d65c36e7de7963c96733ea591338c648cc124fc0b5c9" dependencies = [ "glib", - "glib-sys", - "gobject-sys", "gstreamer", "gstreamer-sdp-sys", - "gstreamer-sys", ] [[package]] name = "gstreamer-sdp-sys" -version = "0.9.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "289c7f258d3387ae91c6058555922d8cf0704fc2bc870d1a03190bc81b040655" +checksum = "e37750f58494510976ac50e6c2ba1757e2270aca1086239bf02f576d63496201" dependencies = [ "glib-sys", - "gobject-sys", "gstreamer-sys", "libc", "system-deps", @@ -412,9 +420,9 @@ dependencies = [ [[package]] name = "gstreamer-sys" -version = "0.9.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1321f34d53bb5f60ab1aaf581e29b664b8d41601714ee1bb7dbea490b5b9ff60" +checksum = "8188ba998999a4a16005c3984812807ff882a87f5f3457c3d5bbbfcbdf631ebd" dependencies = [ "glib-sys", "gobject-sys", @@ -424,57 +432,52 @@ dependencies = [ [[package]] name = "gstreamer-webrtc" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b9ab536c0c208ccc1d2f9f34d9221399fc4b1b498ff0e1f82a283effd2bdb" +checksum = "34da9f45e49d7b56d654d71f0e27a65a4da3096a2b427fdb4782abc164859d60" dependencies = [ "glib", - "glib-sys", - "gobject-sys", "gstreamer", "gstreamer-sdp", - "gstreamer-sys", "gstreamer-webrtc-sys", "libc", ] [[package]] name = "gstreamer-webrtc-sys" -version = "0.9.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca16a3f901e69b6a2f321bf3ec2db9c265469a97daaa27a149102a5a200ddea0" +checksum = "96274f366e272fab68599907fc0f54ba91e8cd15ebdd6554ee5c7c10cd02d6ab" dependencies = [ "glib-sys", - "gobject-sys", "gstreamer-sdp-sys", - "gstreamer-sys", "libc", "system-deps", ] [[package]] name = "heck" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.15" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "http" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ "bytes", "fnv", @@ -483,9 +486,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.4" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" [[package]] name = "humantime" @@ -498,9 +501,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -509,27 +512,27 @@ dependencies = [ [[package]] name = "input_buffer" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ "bytes", ] [[package]] name = "itertools" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "janus-video-room" @@ -546,7 +549,7 @@ dependencies = [ "gstreamer-webrtc", "http", "log", - "rand", + "rand 0.7.3", "serde", "serde_derive", "serde_json", @@ -562,15 +565,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] @@ -583,21 +586,21 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "muldiv" -version = "0.2.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" +checksum = "b5136edda114182728ccdedb9f5eda882781f35fa6e80cc360af12a8932507f3" [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -605,9 +608,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", "num-integer", @@ -616,43 +619,30 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.4.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "opaque-debug" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "paste" -version = "0.1.18" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] +checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" [[package]] name = "percent-encoding" @@ -661,24 +651,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "pin-project" -version = "0.4.23" +name = "pin-project-lite" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" [[package]] name = "pin-utils" @@ -688,36 +664,37 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty-hex" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c" +checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "proc-macro-crate" -version = "0.1.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" dependencies = [ + "thiserror", "toml", ] [[package]] name = "proc-macro-error" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -728,34 +705,32 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "syn", - "syn-mid", "version_check", ] [[package]] name = "proc-macro-hack" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] @@ -768,9 +743,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] @@ -781,11 +756,23 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] @@ -795,7 +782,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", ] [[package]] @@ -804,7 +801,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", ] [[package]] @@ -813,26 +819,34 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", ] [[package]] name = "regex" -version = "1.3.9" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.18" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "ryu" @@ -842,15 +856,15 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" -version = "1.0.114" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" [[package]] name = "serde_derive" -version = "1.0.114" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", "quote", @@ -859,9 +873,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" dependencies = [ "itoa", "ryu", @@ -870,27 +884,34 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.8.2" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81" dependencies = [ "block-buffer", + "cfg-if", + "cpufeatures", "digest", - "fake-simd", "opaque-debug", ] [[package]] name = "slab" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "structopt" -version = "0.3.15" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71" dependencies = [ "clap", "lazy_static", @@ -899,9 +920,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" dependencies = [ "heck", "proc-macro-error", @@ -912,15 +933,15 @@ dependencies = [ [[package]] name = "strum" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" [[package]] name = "strum_macros" -version = "0.18.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2", @@ -930,33 +951,25 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.36" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "system-deps" -version = "1.3.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" +checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" dependencies = [ + "anyhow", + "cfg-expr", "heck", + "itertools", "pkg-config", "strum", "strum_macros", @@ -967,9 +980,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] @@ -985,18 +998,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.20" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.20" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", "quote", @@ -1004,34 +1017,34 @@ dependencies = [ ] [[package]] -name = "thread_local" -version = "1.0.1" +name = "tinyvec" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ - "lazy_static", + "tinyvec_macros", ] [[package]] -name = "tinyvec" -version = "0.3.3" +name = "tinyvec_macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] [[package]] name = "tungstenite" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c" +checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" dependencies = [ "base64", "byteorder", @@ -1040,41 +1053,42 @@ dependencies = [ "httparse", "input_buffer", "log", - "rand", + "rand 0.8.4", "sha-1", + "thiserror", "url", "utf-8", ] [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" dependencies = [ "matches", ] [[package]] name = "unicode-normalization" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" @@ -1084,16 +1098,17 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" -version = "2.1.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ + "form_urlencoded", "idna", "matches", "percent-encoding", @@ -1101,21 +1116,21 @@ dependencies = [ [[package]] name = "utf-8" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "version-compare" -version = "0.0.10" +version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "wasi" @@ -1123,6 +1138,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/webrtc/janus/rust/Cargo.toml b/webrtc/janus/rust/Cargo.toml index 417d959874..33447c53ac 100644 --- a/webrtc/janus/rust/Cargo.toml +++ b/webrtc/janus/rust/Cargo.toml @@ -11,15 +11,15 @@ structopt = { version = "0.3", default-features = false } anyhow = "1" url = "2" rand = "0.7" -async-tungstenite = { version = "0.8", features = ["gio-runtime"] } -gst = { package = "gstreamer", version = "0.16", features = ["v1_14"] } -gst-webrtc = { package = "gstreamer-webrtc", version = "0.16" } -gst-sdp = { package = "gstreamer-sdp", version = "0.16", features = ["v1_14"] } +async-tungstenite = { version = "0.14", features = ["gio-runtime"] } +gst = { package = "gstreamer", version = "0.17", features = ["v1_14"] } +gst-webrtc = { package = "gstreamer-webrtc", version = "0.17" } +gst-sdp = { package = "gstreamer-sdp", version = "0.17", features = ["v1_14"] } serde = "1" serde_derive = "1" serde_json = "1.0.53" http = "0.2" -glib = "0.10" -gio = "0.9" +glib = "0.14" +gio = "0.14" log = "0.4.8" env_logger = "0.7.1" diff --git a/webrtc/janus/rust/src/janus.rs b/webrtc/janus/rust/src/janus.rs index dfecc9230f..730327bdbe 100644 --- a/webrtc/janus/rust/src/janus.rs +++ b/webrtc/janus/rust/src/janus.rs @@ -25,13 +25,13 @@ use { futures::channel::mpsc, futures::sink::{Sink, SinkExt}, futures::stream::{Stream, StreamExt}, - gst::gst_element_error, gst::prelude::*, http::Request, rand::prelude::*, serde_derive::{Deserialize, Serialize}, serde_json::json, std::sync::{Arc, Mutex, Weak}, + std::time::Duration, structopt::StructOpt, tungstenite::Message as WsMessage, }; @@ -215,7 +215,7 @@ impl Peer { let peer = upgrade_weak!(peer_clone); if let Err(err) = peer.on_offer_created(s) { - gst_element_error!( + gst::element_error!( peer.bin, gst::LibraryError::Failed, ("Failed to send SDP offer: {:?}", err) @@ -224,7 +224,7 @@ impl Peer { }); self.webrtcbin - .emit("create-offer", &[&None::, &promise])?; + .emit_by_name("create-offer", &[&None::, &promise])?; Ok(()) } @@ -233,17 +233,15 @@ impl Peer { // WebSocket connection fn on_offer_created(&self, reply: &gst::StructureRef) -> Result<(), anyhow::Error> { let offer = reply - .get_value("offer")? - .get::() - .expect("Invalid argument") - .expect("Invalid offer"); + .get::("offer") + .expect("Invalid argument"); self.webrtcbin - .emit("set-local-description", &[&offer, &None::])?; + .emit_by_name("set-local-description", &[&offer, &None::])?; - info!("sending SDP offer to peer: {:?}", offer.get_sdp().as_text()); + info!("sending SDP offer to peer: {:?}", offer.sdp().as_text()); let transaction = transaction_id(); - let sdp_data = offer.get_sdp().as_text()?; + let sdp_data = offer.sdp().as_text()?; let msg = WsMessage::Text( json!({ "janus": "message", @@ -276,16 +274,14 @@ impl Peer { // WebSocket connection fn on_answer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> { let answer = reply - .get_value("answer")? - .get::() - .expect("Invalid argument") + .get::("answer") .expect("Invalid answer"); self.webrtcbin - .emit("set-local-description", &[&answer, &None::])?; + .emit_by_name("set-local-description", &[&answer, &None::])?; info!( "sending SDP answer to peer: {:?}", - answer.get_sdp().as_text() + answer.sdp().as_text() ); Ok(()) @@ -302,7 +298,7 @@ impl Peer { gst_webrtc::WebRTCSessionDescription::new(gst_webrtc::WebRTCSDPType::Answer, ret); self.webrtcbin - .emit("set-remote-description", &[&answer, &None::])?; + .emit_by_name("set-remote-description", &[&answer, &None::])?; Ok(()) } else if type_ == "offer" { @@ -324,7 +320,7 @@ impl Peer { peer.0 .webrtcbin - .emit("set-remote-description", &[&offer, &None::]) + .emit_by_name("set-remote-description", &[&offer, &None::]) .expect("Unable to set remote description"); let peer_clone = peer.downgrade(); @@ -333,7 +329,7 @@ impl Peer { let peer = upgrade_weak!(peer_clone); if let Err(err) = peer.on_answer_created(&s.to_owned()) { - gst_element_error!( + gst::element_error!( peer.bin, gst::LibraryError::Failed, ("Failed to send SDP answer: {:?}", err) @@ -343,7 +339,7 @@ impl Peer { peer.0 .webrtcbin - .emit("create-answer", &[&None::, &promise]) + .emit_by_name("create-answer", &[&None::, &promise]) .expect("Unable to create answer"); }); @@ -360,7 +356,7 @@ impl Peer { sdp_mline_index, candidate ); self.webrtcbin - .emit("add-ice-candidate", &[&sdp_mline_index, &candidate])?; + .emit_by_name("add-ice-candidate", &[&sdp_mline_index, &candidate])?; Ok(()) } @@ -483,7 +479,7 @@ impl JanusGateway { ws.send(msg).await?; let webrtcbin = pipeline - .get_by_name("webrtcbin") + .by_name("webrtcbin") .expect("can't find webrtcbin"); let webrtc_codec = &args.webrtc_video_codec; @@ -498,14 +494,14 @@ impl JanusGateway { pipeline.add(&encode_bin).expect("Failed to add encode bin"); - let video_queue = pipeline.get_by_name("vqueue").expect("No vqueue found"); - let encoder = encode_bin.get_by_name("encoder").expect("No encoder"); + let video_queue = pipeline.by_name("vqueue").expect("No vqueue found"); + let encoder = encode_bin.by_name("encoder").expect("No encoder"); let srcpad = video_queue - .get_static_pad("src") + .static_pad("src") .expect("Failed to get video queue src pad"); let sinkpad = encoder - .get_static_pad("sink") + .static_pad("sink") .expect("Failed to get sink pad from encoder"); if let Ok(video_ghost_pad) = gst::GhostPad::with_target(Some("video_sink"), &sinkpad) { @@ -514,13 +510,13 @@ impl JanusGateway { } let sinkpad2 = webrtcbin - .get_request_pad("sink_%u") + .request_pad_simple("sink_%u") .expect("Unable to request outgoing webrtcbin pad"); let vsink = encode_bin - .get_by_name("webrtc-vsink") + .by_name("webrtc-vsink") .expect("No webrtc-vsink found"); let srcpad = vsink - .get_static_pad("src") + .static_pad("src") .expect("Element without src pad"); if let Ok(webrtc_ghost_pad) = gst::GhostPad::with_target(Some("webrtc_video_src"), &srcpad) { @@ -528,13 +524,8 @@ impl JanusGateway { webrtc_ghost_pad.link(&sinkpad2)?; } - if let Ok(transceiver) = webrtcbin.emit("get-transceiver", &[&0.to_value()]) { - if let Some(t) = transceiver { - if let Ok(obj) = t.get::() { - obj.expect("Invalid transceiver") - .set_property("do-nack", &true.to_value())?; - } - } + if let Some(transceiver) = webrtcbin.emit_by_name("get-transceiver", &[&0.to_value()]).unwrap().and_then(|val| val.get::().ok()) { + transceiver.set_property("do-nack", &false.to_value())?; } let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::(); @@ -557,7 +548,7 @@ impl JanusGateway { .connect("on-negotiation-needed", false, move |_| { let peer = upgrade_weak!(peer_clone, None); if let Err(err) = peer.on_negotiation_needed() { - gst_element_error!( + gst::element_error!( peer.bin, gst::LibraryError::Failed, ("Failed to negotiate: {:?}", err) @@ -573,16 +564,14 @@ impl JanusGateway { .connect("on-ice-candidate", false, move |values| { let mlineindex = values[1] .get::() - .expect("Invalid argument") .expect("Invalid type"); let candidate = values[2] .get::() - .expect("Invalid argument") .expect("Invalid type"); let peer = upgrade_weak!(peer_clone, None); if let Err(err) = peer.on_ice_candidate(mlineindex, candidate) { - gst_element_error!( + gst::element_error!( peer.bin, gst::LibraryError::Failed, ("Failed to send ICE candidate: {:?}", err) @@ -616,7 +605,7 @@ impl JanusGateway { .expect("Invalid message receiver"); let mut send_ws_msg_rx = send_ws_msg_rx.fuse(); - let timer = glib::interval_stream(10_000); + let timer = glib::interval_stream(Duration::from_secs(10)); let mut timer_fuse = timer.fuse(); let mut sink = self.ws_sink.take().expect("Invalid websocket sink"); @@ -673,7 +662,7 @@ impl JanusGateway { if let Some(sdp) = &jsep.sdp { assert_eq!(jsep.type_, "answer"); let peer = self.peer.lock().expect("Invalid peer"); - return peer.handle_sdp(&jsep.type_, &sdp); + return peer.handle_sdp(&jsep.type_, sdp); } else if let Some(ice) = &jsep.ice { let peer = self.peer.lock().expect("Invalid peer"); return peer.handle_ice(ice.sdp_mline_index, &ice.candidate); diff --git a/webrtc/janus/rust/src/main.rs b/webrtc/janus/rust/src/main.rs index eace4d8fcb..3421945759 100644 --- a/webrtc/janus/rust/src/main.rs +++ b/webrtc/janus/rust/src/main.rs @@ -21,7 +21,6 @@ #![recursion_limit = "256"] use anyhow::bail; -use gst::gst_element_error; use gst::prelude::*; use std::sync::{Arc, Weak}; @@ -77,7 +76,7 @@ impl App { .downcast::() .expect("Couldn't downcast pipeline"); - let bus = pipeline.get_bus().unwrap(); + let bus = pipeline.bus().unwrap(); let app = App(Arc::new(AppInner { pipeline })); let app_weak = app.downgrade(); @@ -99,14 +98,14 @@ impl App { match message.view() { MessageView::Error(err) => bail!( "Error from element {}: {} ({})", - err.get_src() - .map(|s| String::from(s.get_path_string())) + err.src() + .map(|s| String::from(s.path_string())) .unwrap_or_else(|| String::from("None")), - err.get_error(), - err.get_debug().unwrap_or_else(|| String::from("None")), + err.error(), + err.debug().unwrap_or_else(|| String::from("None")), ), MessageView::Warning(warning) => { - println!("Warning: \"{}\"", warning.get_debug().unwrap()); + println!("Warning: \"{}\"", warning.debug().unwrap()); } _ => (), } @@ -121,7 +120,7 @@ impl App { self.pipeline.call_async(|pipeline| { // If this fails, post an error on the bus so we exit if pipeline.set_state(gst::State::Playing).is_err() { - gst_element_error!( + gst::element_error!( pipeline, gst::LibraryError::Failed, ("Failed to set pipeline to Playing") From b27bcc187e867897dcd169cd46f8d9bc403210e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 23 Sep 2021 01:36:15 +0100 Subject: [PATCH 412/412] Release 1.19.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3c6db26d12..71a701889d 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gst-examples', 'c', version : '1.19.1.1', license : 'LGPL') +project('gst-examples', 'c', version : '1.19.2', license : 'LGPL') cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false)