Mostly working player implementation.
authorIlpo Ruotsalainen <ilpo.ruotsalainen@movial.fi>
Tue, 30 Sep 2008 14:15:10 +0000 (17:15 +0300)
committerIlpo Ruotsalainen <ilpo.ruotsalainen@movial.fi>
Tue, 30 Sep 2008 14:15:10 +0000 (17:15 +0300)
isatis-player/player.c

index 7092001..40707a5 100644 (file)
 #include <gst/interfaces/xoverlay.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkx.h>
+#include <glib.h>
 
-static gint xid = -1;
+typedef struct _PlayerInternal
+{
+       gint xid;
+
+       GtkWidget *window;
+       GtkWidget *vbox;
+       GtkWidget *hbox;
+       GtkWidget *darea;
+       GtkWidget *playbutton;
+       GtkWidget *stopbutton;
+       GtkWidget *progressleft;
+       GtkWidget *progressright;
+       GtkWidget *progressbar;
+
+       GstElement *playbin;
+
+       gboolean playing;
+} PlayerInternal;
 
-static GstBusSyncReply prepare_xwindow_id_handler(GstBus *bus, GstMessage *message, GstPipeline *pipeline)
+static void prepare_xwindow_id_callback(GstBus *bus, GstMessage *message, PlayerInternal *priv)
 {
-       if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT ||
-                       !gst_structure_has_name(message->structure, "prepare-xwindow-id"))
-               return GST_BUS_PASS;
+       if (!gst_structure_has_name(message->structure, "prepare-xwindow-id"))
+               return;
 
-       g_debug("prepare-xwindow-id, setting XID to 0x%x", xid);
+       g_debug("prepare-xwindow-id, setting XID to 0x%x", priv->xid);
 
-       gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), xid);
+       gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), priv->xid);
 
        g_object_set(G_OBJECT(GST_MESSAGE_SRC(message)), "force-aspect-ratio", TRUE, NULL);
+}
+
+static gboolean update_progress_callback(PlayerInternal *priv)
+{
+       GstFormat fmt = GST_FORMAT_TIME;
+       gint64 pos, len;
+       char tmp[8];
+
+       if (priv->playing)
+       {
+               if (!gst_element_query_position(priv->playbin, &fmt, &pos))
+                       pos = -1;
+
+               if (!gst_element_query_duration(priv->playbin, &fmt, &len))
+                       len = -1;
+       }
+       else
+               pos = len = -1;
+
+       if (len != -1 && pos != -1)
+       {
+               double fractional_pos = (double)pos / (double)len;
+
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(priv->progressbar), fractional_pos);
+       }
+
+       if (pos != -1)
+       {
+               int secs = pos / 1000000000;
+
+               snprintf(tmp, sizeof(tmp), "%02d:%02d", secs / 60, secs % 60);
+               gtk_label_set_text(GTK_LABEL(priv->progressleft), tmp);
+       }
+       else
+               gtk_label_set_text(GTK_LABEL(priv->progressleft), "--:--");
 
-       gst_message_unref(message);
+       if (len != -1)
+       {
+               int secs = len / 1000000000;
+
+               snprintf(tmp, sizeof(tmp), "%02d:%02d", secs / 60, secs % 60);
+               gtk_label_set_text(GTK_LABEL(priv->progressright), tmp);
+       }
+       else
+       {
+               gtk_label_set_text(GTK_LABEL(priv->progressright), "--:--");
+
+               /* If the length of the stream is unknown clear the progressbar also */
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(priv->progressbar), 0.0);
+       }
+
+       return TRUE;
+}
+
+static void stop_playback(PlayerInternal *priv)
+{
+       /* This is necessary because some versions of gstreamer have a bug that prevents messages for
+        * transitions to GST_STATE_NULL ever being generated/delivered */
 
-       return GST_BUS_DROP;
+       gst_element_set_state(priv->playbin, GST_STATE_NULL);
+
+       priv->playing = FALSE;
+       gtk_widget_set_name(GTK_WIDGET(priv->playbutton), "play-button");
+
+       update_progress_callback(priv);
+
+       /* Invalidate the video area so it gets repainted with background */
+       gdk_window_invalidate_rect(priv->darea->window, NULL, FALSE);
 }
 
-static gboolean bus_message_callback(GstBus *bus, GstMessage *message, gpointer data)
+static void state_changed_callback(GstBus *bus, GstMessage *message, PlayerInternal *priv)
 {
-       g_debug("bus message: %s", GST_MESSAGE_TYPE_NAME(message));
+       GstState old, new, pending;
 
-       return TRUE;
+       /* We only care about the whole playbin pipeline changing state */
+       if (GST_ELEMENT(GST_MESSAGE_SRC(message)) != priv->playbin)
+               return;
+
+       gst_message_parse_state_changed(message, &old, &new, &pending);
+
+       g_debug("pipeline changed state, %d -> %d (want %d)", old, new, pending);
+
+       /* We don't care about intermediate states */
+       if (pending != GST_STATE_VOID_PENDING)
+               return;
+
+       switch (new)
+       {
+               case GST_STATE_PLAYING:
+                       priv->playing = TRUE;
+                       gtk_widget_set_name(GTK_WIDGET(priv->playbutton), "pause-button");
+                       break;
+
+               case GST_STATE_NULL:
+               case GST_STATE_PAUSED:
+                       priv->playing = FALSE;
+                       gtk_widget_set_name(GTK_WIDGET(priv->playbutton), "play-button");
+                       break;
+
+               /* Any final READY state is a bug, we never intend that */
+               case GST_STATE_READY:
+                       g_return_if_reached();
+
+               /* Any unknown state is not a good thing either */
+               default:
+                       g_return_if_reached();
+       }
+
+       update_progress_callback(priv);
+}
+
+static void error_callback(GstBus *bus, GstMessage *message, PlayerInternal *priv)
+{
+       GError *gerror;
+       gchar *debug;
+
+       gst_message_parse_error(message, &gerror, &debug);
+
+       if (gerror)
+       {
+               gchar *tmp;
+
+               gchar *srcname = gst_object_get_name(GST_MESSAGE_SRC(message));
+               g_debug("error from %s: %s (%s)", srcname, gerror->message, debug);
+               g_free(srcname);
+
+               tmp = g_strdup_printf("ERROR: %s", gerror->message);
+               gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), tmp);
+               g_free(tmp);
+       }
+
+       g_error_free(gerror);
+       g_free(debug);
+
+       stop_playback(priv);
+}
+
+static void eos_callback(GstBus *bus, GstMessage *message, PlayerInternal *priv)
+{
+       g_debug("end of stream");
+
+       stop_playback(priv);
 }
 
-static gboolean parse_args(int *argc, char ***argv)
+static gboolean paint_label_background_callback(GtkWidget *widget, GdkEventExpose *event, void *unused)
+{
+       if (GTK_WIDGET_DRAWABLE(widget))
+       {
+               gtk_paint_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+                               &event->area, widget, NULL, widget->allocation.x, widget->allocation.y,
+                               widget->allocation.width, widget->allocation.height);
+       }
+
+       return FALSE;
+}
+
+static void playbutton_clicked_callback(GtkButton *button, PlayerInternal *priv)
+{
+       if (priv->playing)
+               gst_element_set_state(priv->playbin, GST_STATE_PAUSED);
+       else
+               gst_element_set_state(priv->playbin, GST_STATE_PLAYING);
+
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), "");
+}
+
+static void stopbutton_clicked_callback(GtkButton *button, PlayerInternal *priv)
+{
+       stop_playback(priv);
+}
+
+static gboolean parse_args(int *argc, char ***argv, PlayerInternal *priv)
 {
        GOptionEntry options[] =
        {
-               { "xid", 'x', 0, G_OPTION_ARG_INT, &xid, "XID of embed window", NULL },
+               { "xid", 'x', 0, G_OPTION_ARG_INT, &priv->xid, "XID of embed window", NULL },
                { NULL }
        };
        GOptionContext *ctx;
@@ -53,112 +228,85 @@ static gboolean parse_args(int *argc, char ***argv)
        return TRUE;
 }
 
-static gboolean paint_label_background(GtkWidget *widget, GdkEventExpose *event, void *unused)
-{
-       if (GTK_WIDGET_DRAWABLE(widget))
-       {
-               gtk_paint_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
-                               &event->area, widget, NULL, widget->allocation.x, widget->allocation.y,
-                               widget->allocation.width, widget->allocation.height);
-       }
-
-       return FALSE;
-}
-
-static gboolean setup_ui(void)
+static void setup_ui(PlayerInternal *priv)
 {
-       GtkWidget *window;
-       GtkWidget *vbox;
-       GtkWidget *hbox;
-       GtkWidget *darea;
-       GtkWidget *playbutton;
-       GtkWidget *stopbutton;
-       GtkWidget *progressleft;
-       GtkWidget *progressright;
-       GtkWidget *progressbar;
-
        gtk_rc_parse(DATADIR "/isatis-gtkrc");
 
-       if (xid == -1)
+       if (!priv->xid)
        {
                g_debug("creating new window");
 
-               window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+               priv->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        }
        else
        {
-               g_debug("plugging into window 0x%x", xid);
+               g_debug("plugging into window 0x%x", priv->xid);
 
-               window = gtk_plug_new(xid);
-
-               g_debug("plug widget = %p", window);
-
-               g_debug("plug->window = %p", GTK_PLUG(window)->socket_window);
+               priv->window = gtk_plug_new(priv->xid);
        }
 
-       gtk_widget_set_name(GTK_WIDGET(window), "isatis-player");
-
-       hbox = gtk_hbox_new(FALSE, 0);
-
-       playbutton = gtk_button_new();
-       gtk_widget_set_name(GTK_WIDGET(playbutton), "play-button");
-       gtk_widget_set_size_request(GTK_WIDGET(playbutton), 64, 44);
+       gtk_widget_set_name(GTK_WIDGET(priv->window), "isatis-player");
 
-       stopbutton = gtk_button_new();
-       gtk_widget_set_name(GTK_WIDGET(stopbutton), "stop-button");
-       gtk_widget_set_size_request(GTK_WIDGET(stopbutton), 64, 44);
+       priv->hbox = gtk_hbox_new(FALSE, 0);
 
-       progressleft = gtk_label_new("00:00");
-       gtk_widget_set_name(GTK_WIDGET(progressleft), "progress-left");
-       gtk_widget_set_size_request(GTK_WIDGET(progressleft), 60, 44);
-       g_signal_connect(progressleft, "expose-event", G_CALLBACK(paint_label_background), NULL);
+       priv->playbutton = gtk_button_new();
+       gtk_widget_set_name(GTK_WIDGET(priv->playbutton), "play-button");
+       gtk_widget_set_size_request(GTK_WIDGET(priv->playbutton), 64, 44);
+       g_signal_connect(priv->playbutton, "clicked", G_CALLBACK(playbutton_clicked_callback), priv);
 
-       progressright = gtk_label_new("--:--");
-       gtk_widget_set_name(GTK_WIDGET(progressright), "progress-right");
-       gtk_widget_set_size_request(GTK_WIDGET(progressright), 60, 44);
-       g_signal_connect(progressright, "expose-event", G_CALLBACK(paint_label_background), NULL);
+       priv->stopbutton = gtk_button_new();
+       gtk_widget_set_name(GTK_WIDGET(priv->stopbutton), "stop-button");
+       gtk_widget_set_size_request(GTK_WIDGET(priv->stopbutton), 64, 44);
+       g_signal_connect(priv->stopbutton, "clicked", G_CALLBACK(stopbutton_clicked_callback), priv);
 
-       progressbar = gtk_progress_bar_new();
-       gtk_widget_set_name(GTK_WIDGET(progressbar), "progress-bar");
+       priv->progressleft = gtk_label_new("--:--");
+       gtk_widget_set_name(GTK_WIDGET(priv->progressleft), "progress-left");
+       gtk_widget_set_size_request(GTK_WIDGET(priv->progressleft), 60, 44);
+       g_signal_connect(priv->progressleft, "expose-event", G_CALLBACK(paint_label_background_callback), NULL);
 
-       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progressbar), "testing123");
-       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressbar), 1.0);
+       priv->progressright = gtk_label_new("--:--");
+       gtk_widget_set_name(GTK_WIDGET(priv->progressright), "progress-right");
+       gtk_widget_set_size_request(GTK_WIDGET(priv->progressright), 60, 44);
+       g_signal_connect(priv->progressright, "expose-event", G_CALLBACK(paint_label_background_callback), NULL);
 
-       gtk_box_pack_start(GTK_BOX(hbox), playbutton, FALSE, FALSE, 0);
-       gtk_box_pack_start(GTK_BOX(hbox), stopbutton, FALSE, FALSE, 0);
-       gtk_box_pack_end(GTK_BOX(hbox), progressright, FALSE, FALSE, 0);
-       gtk_box_pack_end(GTK_BOX(hbox), progressbar, TRUE, TRUE, 0);
-       gtk_box_pack_end(GTK_BOX(hbox), progressleft, FALSE, FALSE, 0);
+       priv->progressbar = gtk_progress_bar_new();
+       gtk_widget_set_name(GTK_WIDGET(priv->progressbar), "progress-bar");
 
-       vbox = gtk_vbox_new(FALSE, 0);
-       darea = gtk_drawing_area_new();
+       gtk_box_pack_start(GTK_BOX(priv->hbox), priv->playbutton, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(priv->hbox), priv->stopbutton, FALSE, FALSE, 0);
+       gtk_box_pack_end(GTK_BOX(priv->hbox), priv->progressright, FALSE, FALSE, 0);
+       gtk_box_pack_end(GTK_BOX(priv->hbox), priv->progressbar, TRUE, TRUE, 0);
+       gtk_box_pack_end(GTK_BOX(priv->hbox), priv->progressleft, FALSE, FALSE, 0);
 
-       gtk_box_pack_start(GTK_BOX(vbox), darea, TRUE, TRUE, 0);
+       priv->vbox = gtk_vbox_new(FALSE, 0);
+       priv->darea = gtk_drawing_area_new();
 
-       gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(priv->vbox), priv->darea, TRUE, TRUE, 0);
 
-       gtk_container_add(GTK_CONTAINER(window), vbox);
+       gtk_box_pack_end(GTK_BOX(priv->vbox), priv->hbox, FALSE, FALSE, 0);
 
-       gtk_widget_show_all(window);
+       gtk_container_add(GTK_CONTAINER(priv->window), priv->vbox);
 
-       gtk_widget_realize(darea);
-       xid = GDK_WINDOW_XWINDOW(darea->window);
+       gtk_widget_show_all(priv->window);
 
-       g_debug("video pane xid = 0x%x", xid);
+       gtk_widget_realize(priv->darea);
+       priv->xid = GDK_WINDOW_XWINDOW(priv->darea->window);
 
-       return TRUE;
+       g_debug("video pane xid = 0x%x", priv->xid);
 }
 
 int main(int argc, char **argv)
 {
        GMainLoop *mainloop;
-       GstElement *playbin;
        GstBus *bus;
+       PlayerInternal *priv;
 
        gst_init(&argc, &argv);
        gtk_init(&argc, &argv);
 
-       if (!parse_args(&argc, &argv))
+       priv = calloc(1, sizeof(PlayerInternal));
+
+       if (!parse_args(&argc, &argv, priv))
                return 1;
 
        if (argc != 2)
@@ -167,32 +315,27 @@ int main(int argc, char **argv)
                return 1;
        }
 
-       if (!setup_ui())
-               return 2;
-
        mainloop = g_main_loop_new(NULL, FALSE);
 
-       playbin = gst_element_factory_make("playbin", "playbin");
-       g_object_set(G_OBJECT(playbin), "uri", argv[1], NULL);
+       setup_ui(priv);
 
-       bus = gst_element_get_bus(playbin);
-       if (xid != -1)
-               gst_bus_set_sync_handler(bus, (GstBusSyncHandler)prepare_xwindow_id_handler, playbin);
-       gst_bus_add_watch(bus, bus_message_callback, NULL);
-       gst_object_unref(bus);
+       priv->playbin = gst_element_factory_make("playbin", "playbin");
+       g_object_set(G_OBJECT(priv->playbin), "uri", argv[1], NULL);
 
-       if (gst_element_set_state(playbin, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS)
-       {
-               fprintf(stderr, "asdasdfasdf\n");
-               return 2;
-       }
+       bus = gst_element_get_bus(priv->playbin);
+       gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL);
+       gst_bus_add_signal_watch(bus);
 
-       gst_element_set_state(playbin, GST_STATE_PLAYING);
+       g_signal_connect(bus, "sync-message::element", G_CALLBACK(prepare_xwindow_id_callback), priv);
+       g_signal_connect(bus, "message::state-changed", G_CALLBACK(state_changed_callback), priv);
+       g_signal_connect(bus, "message::error", G_CALLBACK(error_callback), priv);
+       g_signal_connect(bus, "message::eos", G_CALLBACK(eos_callback), priv);
 
-       g_main_loop_run(mainloop);
+       g_timeout_add(250, (GSourceFunc)update_progress_callback, priv);
 
-       gst_element_set_state(playbin, GST_STATE_NULL);
-       gst_object_unref(playbin);
+       gst_object_unref(bus);
+
+       g_main_loop_run(mainloop);
 
        return 0;
 }