[snappy/aurena: 1/5] merge between aurena's simple client and snappy that at least builds



commit ca7e9a839039c01cb5c8416de120e175c0a23c70
Author: Luis de Bethencourt <luis debethencourt com>
Date:   Thu Sep 13 19:09:04 2012 +0100

    merge between aurena's simple client and snappy that at least builds

 configure.ac        |   29 +++
 src/Makefile.am     |   20 +-
 src/simple-client.c |  110 ++++++++
 src/snappy.c        |    2 +-
 src/snra-client.c   |  690 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/snra-client.h   |   61 +++++
 src/snra-json.c     |  220 ++++++++++++++++
 src/snra-json.h     |   41 +++
 src/snra-types.h    |   37 +++
 9 files changed, 1202 insertions(+), 8 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5776741..e09b2cf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,8 +40,32 @@ CLUTTER_GST_1_0_REQS=1.4.0
 CLUTTER_GST_0_10_REQS=0.10.0
 GLIB_REQ=2.10.0
 GIO_REQUIRED=2.24
+AVAHI_REQS=0.6.0
+JSON_GLIB_REQS=0.14
+LIBSOUP_REQS=2.4
+
+PKG_CHECK_MODULES([AVAHI_CLIENT],
+    [avahi-client >= $AVAHI_REQS])
+AC_SUBST(AVAHI_CLIENT_CFLAGS)
+AC_SUBST(AVAHI_CLIENT_LIBS)
+
+PKG_CHECK_MODULES([AVAHI_GLIB],
+    [avahi-glib >= $AVAHI_REQS])
+AC_SUBST(AVAHI_GLIB_CFLAGS)
+AC_SUBST(AVAHI_GLIB_LIBS)
+
+PKG_CHECK_MODULES([JSON_GLIB],
+    [json-glib-1.0 >= $JSON_GLIB_REQS])
+AC_SUBST(JSON_GLIB_CFLAGS)
+AC_SUBST(JSON_GLIB_LIBS)
+
+PKG_CHECK_MODULES([LIBSOUP],
+    [libsoup-2.4 >= $LIBSOUP_REQS])
+AC_SUBST(LIBSOUP_CFLAGS)
+AC_SUBST(LIBSOUP_LIBS)
 
 PKG_CHECK_MODULES([GSTREAMER_0_10], \
+
     [gstreamer-0.10 >= $GST_0_10_REQS
     gstreamer-base-0.10 >= $GST_0_10_REQS
     gstreamer-plugins-base-0.10 >= $GST_0_10_REQS])
@@ -58,6 +82,11 @@ PKG_CHECK_MODULES([GST_INTERFACES],
 AC_SUBST(GST_INTERFACES_CFLAGS)
 AC_SUBST(GST_INTERFACES_LIBS)
 
+PKG_CHECK_MODULES([GST_NET],
+    [gstreamer-net-0.10 >= $GST_INTERFACES])
+AC_SUBST(GST_NET_CFLAGS)
+AC_SUBST(GST_NET_LIBS)
+
 PKG_CHECK_MODULES([CLUTTER],
     [clutter-1.0 >= $CLUTTER_REQS])
 AC_SUBST(CLUTTER_CFLAGS)
diff --git a/src/Makefile.am b/src/Makefile.am
index b72ec47..17738d2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,11 +1,14 @@
 NULL =
 
 public_headers = \
-		utils.h \
-		user_interface.h \
-		dlna.h \
-		gst_engine.h \
-		screensaver.h
+	utils.h \
+	user_interface.h \
+	dlna.h \
+	gst_engine.h \
+	snra-client.h \
+	snra-json.h \
+	snra-types.h \
+	screensaver.h
 
 c_sources = \
 	utils.c \
@@ -13,6 +16,9 @@ c_sources = \
 	dlna.c \
 	gst_engine.c \
 	screensaver.c \
+	simple-client.c \
+	snra-client.c \
+	snra-json.c \
 	snappy.c
 
 CLEANFILES =
@@ -20,7 +26,7 @@ CLEANFILES =
 bin_PROGRAMS = snappy
 
 snappy_SOURCES = $(c_sources)
-snappy_CFLAGS = $(CLUTTER_CFLAGS) $(GSTREAMER_0_10_CFLAGS) $(GST_PBUTILS_CFLAGS) $(GST_INTERFACES_CFLAGS) $(CLUTTER_GST_CFLAGS) $(GIO_CFLAGS) $(XTEST_CFLAGS) -DSNAPPY_DATA_DIR="\"$(pkgdatadir)\""
-snappy_LDADD = $(CLUTTER_LIBS) $(GSTREAMER_0_10_LIBS) $(GST_PBUTILS_LIBS) $(GST_INTERFACES_LIBS) $(CLUTTER_GST_LIBS) $(GIO_LIBS) $(XTEST_LIBS)
+snappy_CFLAGS = $(CLUTTER_CFLAGS) $(GSTREAMER_0_10_CFLAGS) $(GST_PBUTILS_CFLAGS) $(GST_NET_CFLAGS) $(GST_INTERFACES_CFLAGS) $(CLUTTER_GST_CFLAGS) $(GIO_CFLAGS) $(XTEST_CFLAGS) $(AVAHI_CLIENT_CFLAGS) $(AVAHI_GLIB_CFLAGS) $(JSON_GLIB_CFLAGS) $(LIBSOUP_CFLAGS) -DSNAPPY_DATA_DIR="\"$(pkgdatadir)\""
+snappy_LDADD = $(CLUTTER_LIBS) $(GSTREAMER_0_10_LIBS) $(GST_PBUTILS_LIBS) $(GST_NET_LIBS) $(GST_INTERFACES_LIBS) $(CLUTTER_GST_LIBS) $(GIO_LIBS) $(XTEST_LIBS) $(AVAHI_CLIENT_LIBS) $(AVAHI_GLIB_LIBS) $(JSON_GLIB_LIBS) $(LIBSOUP_LIBS)
 
 noinst_HEADERS = $(public_headers)
diff --git a/src/simple-client.c b/src/simple-client.c
new file mode 100644
index 0000000..eaee412
--- /dev/null
+++ b/src/simple-client.c
@@ -0,0 +1,110 @@
+/* GStreamer
+ * Copyright (C) 2012 Jan Schmidt <thaytan noraisin net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <avahi-glib/glib-malloc.h>
+
+#include <src/snra-client.h>
+
+static GMainLoop *ml = NULL;
+static gint sigint_received;
+
+static void sigint_handler_sighandler (int signum);
+
+static void
+sigint_setup (void)
+{
+  struct sigaction action;
+  memset (&action, 0, sizeof (struct sigaction));
+
+  action.sa_handler = sigint_handler_sighandler;
+  sigaction (SIGINT, &action, NULL);
+}
+
+static void
+sigint_restore (void)
+{
+  struct sigaction action;
+  memset (&action, 0, sizeof (struct sigaction));
+
+  action.sa_handler = SIG_DFL;
+  sigaction (SIGINT, &action, NULL);
+
+}
+
+static gboolean
+sigint_check (G_GNUC_UNUSED void *data)
+{
+  if (sigint_received) {
+    g_print ("Exiting...\n");
+    g_main_loop_quit (ml);
+  }
+  return TRUE;
+}
+
+static void
+sigint_handler_sighandler (G_GNUC_UNUSED int signum)
+{
+  sigint_received++;
+  sigint_restore ();
+}
+
+int
+main (int argc, char *argv[])
+{
+  SnraClient *client = NULL;
+  int ret = 1;
+  const gchar *server = NULL;
+
+  gst_init (&argc, &argv);
+
+  if (argc > 1) {
+    /* Connect directly to the requested server, no avahi */
+    server = argv[1];
+  }
+
+  avahi_set_allocator (avahi_glib_allocator ());
+
+  g_timeout_add (250, sigint_check, NULL);
+  sigint_setup ();
+
+  client = snra_client_new (server);
+  if (client == NULL)
+    goto fail;
+
+  ml = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (ml);
+
+  ret = 0;
+fail:
+  if (client)
+    g_object_unref (client);
+  if (ml)
+    g_main_loop_unref (ml);
+  return ret;
+}
diff --git a/src/snappy.c b/src/snappy.c
index b9ebe3f..fb80509 100644
--- a/src/snappy.c
+++ b/src/snappy.c
@@ -153,7 +153,7 @@ process_args (int argc, char *argv[],
 
 /*            snappy's main function             */
 int
-main (int argc, char *argv[])
+snappy_main (int argc, char *argv[])
 {
   UserInterface *ui = NULL;
   GstEngine *engine = NULL;
diff --git a/src/snra-client.c b/src/snra-client.c
new file mode 100644
index 0000000..df8d51d
--- /dev/null
+++ b/src/snra-client.c
@@ -0,0 +1,690 @@
+/* GStreamer
+ * Copyright (C) 2012 Jan Schmidt <thaytan noraisin net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Aurena Client is the central object which:
+ *   creates the network clock
+ *   Establishes libsoup session
+ *   Creates RTSP sessions as needed
+ *   Distributes the network clock and base time to clients
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#if !GLIB_CHECK_VERSION(2,22,0)
+/* GResolver not available */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+#include <src/snra-json.h>
+
+#include "snra-client.h"
+
+#define DISABLED_STATE GST_STATE_PAUSED
+
+G_DEFINE_TYPE (SnraClient, snra_client, G_TYPE_OBJECT);
+
+enum
+{
+  PROP_0,
+  PROP_SERVER_HOST,
+  PROP_LAST
+};
+
+static void snra_client_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void snra_client_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static void snra_client_finalize (GObject * object);
+static void snra_client_dispose (GObject * object);
+
+static void search_for_server (SnraClient * client);
+static void connect_to_server (SnraClient * client, const gchar * server,
+    int port);
+static void construct_player (SnraClient * client);
+
+static gboolean
+try_reconnect (SnraClient * client)
+{
+  client->timeout = 0;
+
+  if (client->server_host)
+    connect_to_server (client, client->server_host, client->server_port);
+  else
+    search_for_server (client);
+
+  return FALSE;
+}
+
+static void
+handle_connection_closed_cb (G_GNUC_UNUSED SoupSession * session,
+    SoupMessage * msg, SnraClient * client)
+{
+  client->connecting = FALSE;
+
+  if (msg->status_code == SOUP_STATUS_CANCELLED)
+    return;
+
+  if (client->was_connected) {
+    g_print ("Disconnected from server. Reason %s status %d\n",
+        msg->reason_phrase, msg->status_code);
+  }
+  client->was_connected = FALSE;
+
+  if (client->player)
+    gst_element_set_state (client->player, GST_STATE_READY);
+  if (client->timeout == 0) {
+    client->timeout =
+        g_timeout_add_seconds (1, (GSourceFunc) try_reconnect, client);
+  }
+}
+
+static void
+handle_enrol_message (SnraClient * client, GstStructure * s)
+{
+  int clock_port;
+  gint64 tmp;
+  GstClockTime cur_time;
+  gchar *server_ip_str = NULL;
+  gdouble new_vol;
+
+  if (!snra_json_structure_get_int (s, "clock-port", &clock_port))
+    return;                     /* Invalid message */
+
+  if (!snra_json_structure_get_int64 (s, "current-time", &tmp))
+    return;                     /* Invalid message */
+  cur_time = (GstClockTime) (tmp);
+
+  if (snra_json_structure_get_double (s, "volume-level", &new_vol)) {
+    if (client->player == NULL)
+      construct_player (client);
+
+    if (client->player) {
+      //g_print ("New volume %g\n", new_vol);
+      g_object_set (G_OBJECT (client->player), "volume", new_vol,
+          "mute", (gboolean) (new_vol == 0.0), NULL);
+    }
+  }
+
+  snra_json_structure_get_boolean (s, "enabled", &client->enabled);
+  snra_json_structure_get_boolean (s, "paused", &client->paused);
+
+#if GLIB_CHECK_VERSION(2,22,0)
+  {
+    GResolver *resolver = g_resolver_get_default ();
+    GList *names;
+
+    if (resolver == NULL)
+      return;
+
+    names =
+        g_resolver_lookup_by_name (resolver, client->connected_server, NULL,
+        NULL);
+    if (names) {
+      server_ip_str = g_inet_address_to_string ((GInetAddress *) (names->data));
+      g_resolver_free_addresses (names);
+    }
+    g_object_unref (resolver);
+  }
+#else
+  {
+    struct addrinfo *names = NULL;
+    if (getaddrinfo (client->connected_server, NULL, NULL, &names))
+      return;
+    if (names) {
+      char hbuf[NI_MAXHOST];
+      if (getnameinfo (names->ai_addr, names->ai_addrlen,
+              hbuf, sizeof (hbuf), NULL, 0, NI_NUMERICHOST) == 0) {
+        server_ip_str = g_strdup (hbuf);
+      }
+      freeaddrinfo (names);
+    }
+  }
+#endif
+  if (server_ip_str) {
+    g_print ("Creating net clock at %s:%d time %" GST_TIME_FORMAT "\n",
+        server_ip_str, clock_port, GST_TIME_ARGS (cur_time));
+    if (client->net_clock)
+      gst_object_unref (client->net_clock);
+    client->net_clock = gst_net_client_clock_new ("net_clock", server_ip_str,
+        clock_port, cur_time);
+    g_free (server_ip_str);
+  }
+}
+
+static void
+on_eos_msg (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
+    SnraClient * client)
+{
+  SoupMessage *soup_msg;
+  /* FIXME: Next song should all be handled server side */
+  char *url = g_strdup_printf ("http://%s:%u/control/next";,
+      client->connected_server, client->connected_port);
+
+  g_print ("Got EOS message\n");
+
+  soup_msg = soup_message_new ("GET", url);
+  soup_session_send_message (client->soup, soup_msg);
+  g_free (url);
+}
+
+static void
+on_error_msg (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
+    G_GNUC_UNUSED SnraClient * client)
+{
+  GError *err;
+  gchar *dbg_info = NULL;
+
+  gst_message_parse_error (msg, &err, &dbg_info);
+  g_printerr ("ERROR from element %s: %s\n",
+      GST_OBJECT_NAME (msg->src), err->message);
+  g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+  g_error_free (err);
+  g_free (dbg_info);
+}
+
+static void
+construct_player (SnraClient * client)
+{
+  GstBus *bus;
+
+  if (GST_CHECK_VERSION (0, 11, 1))
+    client->player = gst_element_factory_make ("playbin", NULL);
+  else
+    client->player = gst_element_factory_make ("playbin2", NULL);
+
+  if (client->player == NULL) {
+    g_warning ("Failed to construct playbin");
+    return;
+  }
+  bus = gst_element_get_bus (GST_ELEMENT (client->player));
+  gst_bus_add_signal_watch (bus);
+  g_signal_connect (bus, "message::eos", (GCallback) (on_eos_msg), client);
+  g_signal_connect (bus, "message::error", (GCallback) (on_error_msg), client);
+  gst_object_unref (bus);
+}
+
+static void
+handle_set_media_message (SnraClient * client, GstStructure * s)
+{
+  const gchar *protocol, *path;
+  int port;
+  GstClockTime base_time;
+  gint64 tmp;
+  gchar *uri;
+  gboolean paused;
+
+  protocol = gst_structure_get_string (s, "resource-protocol");
+  path = gst_structure_get_string (s, "resource-path");
+
+  if (protocol == NULL || path == NULL)
+    return;                     /* Invalid message */
+
+  if (!snra_json_structure_get_int (s, "resource-port", &port))
+    return;
+
+  if (!snra_json_structure_get_int64 (s, "base-time", &tmp))
+    return;                     /* Invalid message */
+
+  if (!snra_json_structure_get_boolean (s, "paused", &paused))
+    return;
+
+  base_time = (GstClockTime) (tmp);
+
+  if (client->player == NULL) {
+    construct_player (client);
+    if (client->player == NULL)
+      return;
+  } else {
+    gst_element_set_state (client->player, GST_STATE_NULL);
+  }
+
+  uri =
+      g_strdup_printf ("%s://%s:%d%s", protocol, client->connected_server, port,
+      path);
+  g_print ("Playing URI %s base_time %" GST_TIME_FORMAT "\n", uri,
+      GST_TIME_ARGS (base_time));
+  g_object_set (client->player, "uri", uri, NULL);
+  g_free (uri);
+
+  gst_element_set_start_time (client->player, GST_CLOCK_TIME_NONE);
+  gst_element_set_base_time (client->player, base_time);
+  gst_pipeline_use_clock (GST_PIPELINE (client->player), client->net_clock);
+
+  if (client->enabled) {
+    if (paused)
+      client->state = GST_STATE_PAUSED;
+    else
+      client->state = GST_STATE_PLAYING;
+  } else {
+    client->state = DISABLED_STATE;
+  }
+
+  gst_element_set_state (client->player, client->state);
+}
+
+static void
+handle_play_message (SnraClient * client, GstStructure * s)
+{
+  GstClockTime base_time;
+  gint64 tmp;
+
+  if (!snra_json_structure_get_int64 (s, "base-time", &tmp))
+    return;                     /* Invalid message */
+  base_time = (GstClockTime) (tmp);
+
+  client->paused = FALSE;
+
+  if (client->player) {
+    GstClockTime stream_time =
+        gst_clock_get_time (client->net_clock) - base_time;
+    g_print ("Playing base_time %" GST_TIME_FORMAT " (offset %" GST_TIME_FORMAT
+        ")\n", GST_TIME_ARGS (base_time), GST_TIME_ARGS (stream_time));
+    gst_element_set_base_time (GST_ELEMENT (client->player), base_time);
+    if (client->enabled == FALSE)
+      client->state = DISABLED_STATE;
+    else
+      client->state = GST_STATE_PLAYING;
+    gst_element_set_state (GST_ELEMENT (client->player), client->state);
+  }
+}
+
+static void
+handle_set_volume_message (SnraClient * client, GstStructure * s)
+{
+  gdouble new_vol;
+
+  if (!snra_json_structure_get_double (s, "level", &new_vol))
+    return;
+
+  if (client->player == NULL)
+    construct_player (client);
+
+  if (client->player) {
+    // g_print ("New volume %g\n", new_vol);
+    g_object_set (G_OBJECT (client->player), "volume", new_vol,
+        "mute", (gboolean) (new_vol == 0.0), NULL);
+  }
+}
+
+static void
+handle_set_client_message (SnraClient * client, GstStructure * s)
+{
+  if (!snra_json_structure_get_boolean (s, "enabled", &client->enabled))
+    return;
+
+  if (client->enabled == FALSE)
+    client->state = DISABLED_STATE;
+  else if (client->paused)
+    client->state = GST_STATE_PAUSED;
+  else
+    client->state = GST_STATE_PLAYING;
+
+  if (client->player)
+    gst_element_set_state (GST_ELEMENT (client->player), client->state);
+}
+
+static void
+handle_received_chunk (G_GNUC_UNUSED SoupMessage * msg, SoupBuffer * chunk,
+    SnraClient * client)
+{
+  if (client->was_connected == FALSE) {
+    g_print ("Successfully connected to server %s:%d\n",
+        client->connected_server, client->connected_port);
+
+    client->was_connected = TRUE;
+  }
+  /* Successful server connection, stop avahi discovery */
+  if (client->avahi_client) {
+    avahi_client_free (client->avahi_client);
+    client->avahi_sb = NULL;
+    client->avahi_client = NULL;
+  }
+
+  if (client->json == NULL)
+    client->json = json_parser_new ();
+#if 0
+  {
+    gchar *tmp = g_strndup (chunk->data, chunk->length);
+    g_print ("%s\n", tmp);
+    g_free (tmp);
+  }
+#endif
+  if (json_parser_load_from_data (client->json, chunk->data, chunk->length,
+          NULL)) {
+    JsonNode *root = json_parser_get_root (client->json);
+    GstStructure *s = snra_json_to_gst_structure (root);
+    const char *msg_type;
+
+    if (s == NULL)
+      return;                   /* Invalid chunk */
+
+    msg_type = gst_structure_get_string (s, "msg-type");
+    if (msg_type == NULL || g_str_equal (msg_type, "ping")) {
+      gst_structure_free (s);
+      return;
+    }
+
+    if (g_str_equal (msg_type, "enrol"))
+      handle_enrol_message (client, s);
+    else if (g_str_equal (msg_type, "set-media"))
+      handle_set_media_message (client, s);
+    else if (g_str_equal (msg_type, "play"))
+      handle_play_message (client, s);
+    else if (g_str_equal (msg_type, "pause")) {
+      client->paused = TRUE;
+      if (client->enabled == FALSE)
+        client->state = DISABLED_STATE;
+      else
+        client->state = GST_STATE_PAUSED;
+      if (client->player)
+        gst_element_set_state (GST_ELEMENT (client->player), client->state);
+    } else if (g_str_equal (msg_type, "volume")) {
+      handle_set_volume_message (client, s);
+    } else if (g_str_equal (msg_type, "client-setting")) {
+      handle_set_client_message (client, s);
+    } else {
+      g_print ("Unhandled event of type %s\n", msg_type);
+    }
+  }
+}
+
+static void
+connect_to_server (SnraClient * client, const gchar * server, int port)
+{
+  SoupMessage *msg;
+  char *url = g_strdup_printf ("http://%s:%u/client/player_events";,
+      server, port);
+  if (client->connecting == FALSE) {
+    g_print ("Attemping to connect to server %s:%d\n", server, port);
+    client->connecting = TRUE;
+  }
+  g_free (client->connected_server);
+  client->connected_server = g_strdup (server);
+  client->connected_port = port;
+
+  msg = soup_message_new ("GET", url);
+  soup_message_body_set_accumulate (msg->response_body, FALSE);
+  g_signal_connect (msg, "got-chunk", (GCallback) handle_received_chunk,
+      client);
+  soup_session_queue_message (client->soup, msg,
+      (SoupSessionCallback) handle_connection_closed_cb, client);
+  g_free (url);
+}
+
+static void
+snra_client_init (SnraClient * client)
+{
+  client->soup = soup_session_async_new ();
+  client->server_port = 5457;
+  client->state = GST_STATE_NULL;
+}
+
+static void
+snra_client_constructed (GObject * object)
+{
+  SnraClient *client = (SnraClient *) (object);
+
+  if (G_OBJECT_CLASS (snra_client_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (snra_client_parent_class)->constructed (object);
+
+  try_reconnect (client);
+}
+
+static void
+snra_client_class_init (SnraClientClass * client_class)
+{
+  GObjectClass *gobject_class = (GObjectClass *) (client_class);
+
+  gobject_class->constructed = snra_client_constructed;
+  gobject_class->dispose = snra_client_dispose;
+  gobject_class->finalize = snra_client_finalize;
+
+  gobject_class->set_property = snra_client_set_property;
+  gobject_class->get_property = snra_client_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_SERVER_HOST,
+      g_param_spec_string ("server-host", "Aurena Server",
+          "Aurena Server hostname or IP", NULL,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+snra_client_finalize (GObject * object)
+{
+  SnraClient *client = (SnraClient *) (object);
+
+  if (client->avahi_sb)
+    avahi_service_browser_free (client->avahi_sb);
+  if (client->avahi_client)
+    avahi_client_free (client->avahi_client);
+  if (client->glib_poll)
+    avahi_glib_poll_free (client->glib_poll);
+
+  if (client->net_clock)
+    gst_object_unref (client->net_clock);
+  if (client->soup)
+    g_object_unref (client->soup);
+  if (client->json)
+    g_object_unref (client->json);
+  if (client->player) {
+    GstBus *bus = gst_element_get_bus (client->player);
+    gst_bus_remove_signal_watch (bus);
+    gst_object_unref (bus);
+    gst_object_unref (client->player);
+  }
+
+  g_free (client->server_host);
+  g_free (client->connected_server);
+
+  G_OBJECT_CLASS (snra_client_parent_class)->finalize (object);
+}
+
+static void
+snra_client_dispose (GObject * object)
+{
+  SnraClient *client = (SnraClient *) (object);
+
+  if (client->soup)
+    soup_session_abort (client->soup);
+  if (client->player)
+    gst_element_set_state (client->player, GST_STATE_NULL);
+
+  G_OBJECT_CLASS (snra_client_parent_class)->dispose (object);
+}
+
+static void
+split_server_host (SnraClient * client)
+{
+  /* See if the client->server_host string has a : and split into
+   * server:port if so */
+  gchar *sep = g_strrstr (client->server_host, ":");
+
+  if (sep) {
+    gchar *server = g_strndup (client->server_host, sep - client->server_host);
+
+    client->server_port = atoi (sep + 1);
+    g_free (client->server_host);
+    client->server_host = server;
+  }
+}
+
+static void
+snra_client_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  SnraClient *client = (SnraClient *) (object);
+
+  switch (prop_id) {
+    case PROP_SERVER_HOST:
+      if (client->server_host)
+        g_free (client->server_host);
+      client->server_host = g_value_dup_string (value);
+      if (client->server_host)
+        split_server_host (client);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+snra_client_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  SnraClient *client = (SnraClient *) (object);
+
+  switch (prop_id) {
+    case PROP_SERVER_HOST:{
+      gchar *tmp =
+          g_strdup_printf ("%s:%u", client->server_host, client->server_port);
+      g_value_take_string (value, tmp);
+      break;
+    }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+SnraClient *
+snra_client_new (const char *server_host)
+{
+  SnraClient *client = g_object_new (SNRA_TYPE_CLIENT,
+      "server-host", server_host, NULL);
+  return client;
+}
+
+static void
+avahi_resolve_callback (AvahiServiceResolver * r,
+    AVAHI_GCC_UNUSED AvahiIfIndex interface,
+    AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event,
+    AVAHI_GCC_UNUSED const char *name, AVAHI_GCC_UNUSED const char *type,
+    AVAHI_GCC_UNUSED const char *domain, const char *host_name,
+    AVAHI_GCC_UNUSED const AvahiAddress * address, uint16_t port,
+    AVAHI_GCC_UNUSED AvahiStringList * txt,
+    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+    AVAHI_GCC_UNUSED void *userdata)
+{
+  SnraClient *client = userdata;
+
+  switch (event) {
+    case AVAHI_RESOLVER_FAILURE:
+      break;
+
+    case AVAHI_RESOLVER_FOUND:{
+      if (!client->connecting) {
+        /* FIXME: Build a list of servers and try each one in turn? */
+        connect_to_server (client, host_name, port);
+      }
+    }
+  }
+
+  avahi_service_resolver_free (r);
+}
+
+static void
+browse_callback (AVAHI_GCC_UNUSED AvahiServiceBrowser * b,
+    AvahiIfIndex interface, AvahiProtocol protocol,
+    AvahiBrowserEvent event,
+    const char *name, const char *type, const char *domain,
+    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata)
+{
+  SnraClient *client = userdata;
+
+  switch (event) {
+    case AVAHI_BROWSER_FAILURE:
+      /* Respawn browser on a timer? */
+      avahi_service_browser_free (client->avahi_sb);
+      client->timeout = g_timeout_add_seconds (1,
+          (GSourceFunc) try_reconnect, client);
+      return;
+
+    case AVAHI_BROWSER_NEW:{
+      avahi_service_resolver_new (client->avahi_client, interface,
+          protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0,
+          avahi_resolve_callback, client);
+      break;
+    }
+    case AVAHI_BROWSER_REMOVE:
+    case AVAHI_BROWSER_ALL_FOR_NOW:
+    case AVAHI_BROWSER_CACHE_EXHAUSTED:
+      break;
+  }
+}
+
+static void
+snra_avahi_client_callback (AvahiClient * s, AvahiClientState state,
+    SnraClient * client)
+{
+  switch (state) {
+    case AVAHI_CLIENT_S_RUNNING:{
+      if (client->avahi_sb == NULL) {
+        g_print ("Looking for new broadcast servers\n");
+        client->avahi_sb = avahi_service_browser_new (s, AVAHI_IF_UNSPEC,
+            AVAHI_PROTO_UNSPEC, "_aurena._tcp", NULL, 0, browse_callback,
+            client);
+        if (client->avahi_sb == NULL) {
+          fprintf (stderr, "Failed to create service browser: %s\n",
+              avahi_strerror (avahi_client_errno (client->avahi_client)));
+        }
+      }
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+static void
+search_for_server (SnraClient * client)
+{
+  const AvahiPoll *poll_api;
+  int error;
+
+  if (client->glib_poll == NULL) {
+    client->glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
+    if (client->glib_poll == NULL)
+      return;
+  }
+
+  poll_api = avahi_glib_poll_get (client->glib_poll);
+
+  if (client->avahi_client == NULL) {
+    client->avahi_client =
+        avahi_client_new (poll_api, AVAHI_CLIENT_NO_FAIL,
+        (AvahiClientCallback) snra_avahi_client_callback, client, &error);
+    if (client->avahi_client == NULL) {
+      fprintf (stderr, "Failed to connect to Avahi: %s",
+          avahi_strerror (error));
+      return;
+    }
+  }
+
+}
diff --git a/src/snra-client.h b/src/snra-client.h
new file mode 100644
index 0000000..e98f1e2
--- /dev/null
+++ b/src/snra-client.h
@@ -0,0 +1,61 @@
+#ifndef __SNRA_CLIENT_H__
+#define __SNRA_CLIENT_H__
+
+#include <gst/gst.h>
+#include <gst/net/gstnet.h>
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-glib/glib-watch.h>
+
+#include <src/snra-types.h>
+
+G_BEGIN_DECLS
+
+#define SNRA_TYPE_CLIENT (snra_client_get_type ())
+
+typedef struct _SnraClientClass SnraClientClass;
+
+struct _SnraClient
+{
+  GObject parent;
+
+  GstState state;
+  gboolean enabled;
+  gboolean paused;
+
+  GstClock *net_clock;
+  gchar *server_host;
+  gint server_port;
+
+  SoupSession *soup;
+  JsonParser *json;
+
+  GstElement *player;
+
+  guint timeout;
+
+  gboolean connecting;
+  gboolean was_connected;
+  gchar *connected_server;
+  gint connected_port;
+
+  AvahiGLibPoll *glib_poll;
+  AvahiClient *avahi_client;
+  AvahiServiceBrowser *avahi_sb;
+};
+
+struct _SnraClientClass
+{
+  GObjectClass parent;
+};
+
+GType snra_client_get_type(void);
+SnraClient *snra_client_new(const gchar *server);
+
+G_END_DECLS
+#endif
diff --git a/src/snra-json.c b/src/snra-json.c
new file mode 100644
index 0000000..e5db7f1
--- /dev/null
+++ b/src/snra-json.c
@@ -0,0 +1,220 @@
+/* GStreamer
+ * Copyright (C) 2012 Jan Schmidt <thaytan noraisin net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <json-glib/json-glib.h>
+
+#include <src/snra-json.h>
+
+#ifndef G_VALUE_INIT
+#define G_VALUE_INIT {{0,}, {0,}}
+#endif
+
+static void
+snra_json_array_add_to_val (JsonArray *array, guint index_,
+    JsonNode *element_node, GValue *outval);
+
+static void
+snra_json_node_into_val (JsonNode *element_node, GValue *v)
+{
+  if (JSON_NODE_HOLDS_OBJECT (element_node)) {
+    GstStructure *child = snra_json_to_gst_structure (element_node);
+    g_value_init (v, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (v, child);
+  } if (JSON_NODE_HOLDS_ARRAY (element_node)) {
+    JsonArray *arr = json_node_get_array (element_node);
+    g_value_init (v, GST_TYPE_ARRAY);
+    json_array_foreach_element (arr,
+        (JsonArrayForeach) snra_json_array_add_to_val, v);
+  } else {
+    json_node_get_value (element_node, v);
+  }
+}
+
+static void
+snra_json_array_add_to_val (G_GNUC_UNUSED JsonArray *array,
+    G_GNUC_UNUSED guint index_,
+    JsonNode *element_node, GValue *outval)
+{
+  GValue v = G_VALUE_INIT;
+  snra_json_node_into_val (element_node, &v);
+  gst_value_array_append_value (outval, &v); 
+  g_value_unset (&v);
+}
+
+static void
+snra_gst_struct_from_object (G_GNUC_UNUSED JsonObject * o,
+    const gchar * member_name, JsonNode * member_node, GstStructure * s)
+{
+  GValue v = G_VALUE_INIT;
+  snra_json_node_into_val (member_node, &v);
+  gst_structure_set_value (s, member_name, &v);
+  g_value_unset (&v);
+}
+
+GstStructure *
+snra_json_to_gst_structure (JsonNode * root)
+{
+  GstStructure *s = NULL;
+  JsonObject *o;
+
+  if (!JSON_NODE_HOLDS_OBJECT (root))
+    return NULL;
+
+  s = gst_structure_new ("json", NULL, NULL);
+
+  o = json_node_get_object (root);
+  json_object_foreach_member (o,
+      (JsonObjectForeach) snra_gst_struct_from_object, s);
+
+  return s;
+}
+
+static JsonNode *
+snra_json_value_to_node (const GValue *value)
+{
+  JsonNode *n = NULL;
+
+  if (GST_VALUE_HOLDS_STRUCTURE (value)) {
+    const GstStructure *s = gst_value_get_structure (value);
+    n = snra_json_from_gst_structure (s);
+  }
+  else if (GST_VALUE_HOLDS_ARRAY (value)) {
+    guint count = gst_value_array_get_size (value);
+    guint i;
+    JsonArray *arr = json_array_sized_new (count);
+    for (i = 0; i < count; i++) {
+      const GValue *sub_val = gst_value_array_get_value (value, i);
+      JsonNode *tmp = snra_json_value_to_node (sub_val);
+      if (tmp)
+        json_array_add_element (arr, tmp);
+    }
+    n = json_node_new (JSON_NODE_ARRAY);
+    json_node_take_array (n, arr);
+  } else {
+    n = json_node_new (JSON_NODE_VALUE);
+    json_node_set_value (n, value);
+  }
+
+  return n;
+}
+
+static void
+snra_add_struct_object (GQuark field_id, const GValue * value, JsonObject * o)
+{
+  JsonNode *n = snra_json_value_to_node (value);
+  if (n)
+    json_object_set_member (o, g_quark_to_string (field_id), n);
+}
+
+JsonNode *
+snra_json_from_gst_structure (const GstStructure * s)
+{
+  JsonNode *root = json_node_new (JSON_NODE_OBJECT);
+
+  json_node_take_object (root, json_object_new ());
+
+  gst_structure_foreach (s,
+      (GstStructureForeachFunc) snra_add_struct_object,
+      json_node_get_object (root));
+
+  return root;
+}
+
+static gboolean
+snra_json_structure_get_as (const GstStructure * structure,
+    const gchar * fieldname, GType t, GValue * dest)
+{
+  const GValue *v1 = gst_structure_get_value (structure, fieldname);
+  if (v1 == NULL)
+    return FALSE;
+
+  g_value_init (dest, t);
+  g_value_transform (v1, dest);
+
+  return TRUE;
+}
+
+gboolean
+snra_json_structure_get_int (const GstStructure * structure,
+    const gchar * fieldname, gint * value)
+{
+  GValue dest = G_VALUE_INIT;
+  gboolean res =
+      snra_json_structure_get_as (structure, fieldname, G_TYPE_INT, &dest);
+
+  if (res) {
+    if (value)
+      *value = g_value_get_int (&dest);
+    g_value_unset (&dest);
+  }
+  return TRUE;
+}
+
+gboolean
+snra_json_structure_get_int64 (const GstStructure * structure,
+    const gchar * fieldname, gint64 * value)
+{
+  GValue dest = G_VALUE_INIT;
+  gboolean res =
+      snra_json_structure_get_as (structure, fieldname, G_TYPE_INT64, &dest);
+
+  if (res) {
+    if (value)
+      *value = g_value_get_int64 (&dest);
+    g_value_unset (&dest);
+  }
+  return res;
+}
+
+gboolean
+snra_json_structure_get_double (const GstStructure * structure,
+    const gchar * fieldname, gdouble * value)
+{
+  GValue dest = G_VALUE_INIT;
+  gboolean res =
+      snra_json_structure_get_as (structure, fieldname, G_TYPE_DOUBLE, &dest);
+
+  if (res) {
+    if (value)
+      *value = g_value_get_double (&dest);
+    g_value_unset (&dest);
+  }
+  return res;
+}
+
+gboolean
+snra_json_structure_get_boolean (const GstStructure * structure,
+    const gchar * fieldname, gboolean * value)
+{
+  GValue dest = G_VALUE_INIT;
+  gboolean res = snra_json_structure_get_as (structure, fieldname,
+      G_TYPE_BOOLEAN, &dest);
+
+  if (res) {
+    if (value)
+      *value = g_value_get_boolean (&dest);
+    g_value_unset (&dest);
+  }
+  return res;
+}
diff --git a/src/snra-json.h b/src/snra-json.h
new file mode 100644
index 0000000..d0c065f
--- /dev/null
+++ b/src/snra-json.h
@@ -0,0 +1,41 @@
+/* GStreamer
+ * Copyright (C) 2012 Jan Schmidt <thaytan noraisin net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SNRA_JSON_H__
+#define __SNRA_JSON_H__
+
+#include <gst/gst.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+GstStructure *snra_json_to_gst_structure (JsonNode *root);
+JsonNode *snra_json_from_gst_structure (const GstStructure *s);
+gboolean snra_json_structure_get_int (const GstStructure *structure,
+    const gchar *fieldname, gint *value);
+gboolean snra_json_structure_get_int64 (const GstStructure *structure,
+    const gchar *fieldname, gint64 *value);
+gboolean snra_json_structure_get_double (const GstStructure *structure,
+    const gchar *fieldname, gdouble *value);
+gboolean snra_json_structure_get_boolean (const GstStructure *structure,
+    const gchar *fieldname, gboolean *value);
+
+G_END_DECLS
+
+#endif
diff --git a/src/snra-types.h b/src/snra-types.h
new file mode 100644
index 0000000..ec69ed0
--- /dev/null
+++ b/src/snra-types.h
@@ -0,0 +1,37 @@
+/* GStreamer
+ * Copyright (C) 2012 Jan Schmidt <thaytan noraisin net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __SRNA_TYPES_H__
+#define __SRNA_TYPES_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SnraAvahi SnraAvahi;
+typedef struct _SnraClient SnraClient;
+typedef struct _SnraConfig SnraConfig;
+typedef struct _SnraHttpResource SnraHttpResource;
+typedef struct _SnraManager SnraManager;
+typedef struct _SnraMediaDB SnraMediaDB;
+typedef struct _SnraServer SnraServer;
+typedef struct _SnraServerClient SnraServerClient;
+
+G_END_DECLS
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]