summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastian Winkler <buz@netbuz.org>2013-05-03 15:40:07 +0200
committerLionel Landwerlin <llandwerlin@gmail.com>2013-07-08 12:09:35 +0100
commita661cbdca1c5a807ab48017cd4c93c149f878b0b (patch)
treee250c4b8e9716ebcafe387e2f953fa6138e33825
parent90577622f044adb722154ba42a19a81a9a7d56d1 (diff)
downloadclutter-gst-a661cbdca1c5a807ab48017cd4c93c149f878b0b.tar.gz
Add ClutterGstContent
This adds a ClutterContent implementation that uses a CoglGstVideoSink to display GStreamer video frames in any ClutterActor. It does not implement the ClutterGstPlayer interface, so you have to manage all GStreamer related tasks on your own. https://bugzilla.gnome.org/show_bug.cgi?id=699825
-rw-r--r--clutter-gst/Makefile.am2
-rw-r--r--clutter-gst/clutter-gst-content.c397
-rw-r--r--clutter-gst/clutter-gst-content.h91
-rw-r--r--clutter-gst/clutter-gst.h1
-rw-r--r--examples/.gitignore1
-rw-r--r--examples/Makefile.am9
-rw-r--r--examples/video-content.c172
7 files changed, 672 insertions, 1 deletions
diff --git a/clutter-gst/Makefile.am b/clutter-gst/Makefile.am
index 661e69c..ccb8e04 100644
--- a/clutter-gst/Makefile.am
+++ b/clutter-gst/Makefile.am
@@ -33,6 +33,7 @@ source_h = \
$(srcdir)/clutter-gst-aspectratio.h \
$(srcdir)/clutter-gst-crop.h \
$(srcdir)/clutter-gst-pipeline.h \
+ $(srcdir)/clutter-gst-content.h \
$(NULL)
source_priv_h = \
@@ -54,6 +55,7 @@ source_c = \
$(srcdir)/clutter-gst-aspectratio.c \
$(srcdir)/clutter-gst-crop.c \
$(srcdir)/clutter-gst-pipeline.c \
+ $(srcdir)/clutter-gst-content.c \
$(glib_enum_c) \
$(NULL)
diff --git a/clutter-gst/clutter-gst-content.c b/clutter-gst/clutter-gst-content.c
new file mode 100644
index 0000000..2a8fea7
--- /dev/null
+++ b/clutter-gst/clutter-gst-content.c
@@ -0,0 +1,397 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * Authored By Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
+ * Bastian Winkler <buz@netbuz.org>
+ *
+ * Copyright (C) 2013 Intel Corporation
+ * Copyright (C) 2013 Bastian Winkler <buz@netbuz.org>
+ *
+ * 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 "clutter-gst-content.h"
+#include "clutter-gst-private.h"
+#include "clutter-gst-marshal.h"
+
+static void clutter_content_iface_init (ClutterContentIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ClutterGstContent, clutter_gst_content, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
+ clutter_content_iface_init));
+
+#define CLUTTER_GST_CONTENT_GET_PRIVATE(obj)\
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ CLUTTER_GST_TYPE_CONTENT, \
+ ClutterGstContentPrivate))
+
+
+struct _ClutterGstContentPrivate
+{
+ CoglGstVideoSink *sink;
+ ClutterGstFrame *current_frame;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_VIDEO_SINK,
+
+ PROP_LAST
+};
+
+static GParamSpec *props[PROP_LAST];
+
+enum
+{
+ SIZE_CHANGE,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+
+static void
+update_frame (ClutterGstContent *self,
+ CoglPipeline *pipeline)
+{
+ ClutterGstContentPrivate *priv = self->priv;
+ ClutterGstFrame *old_frame, *new_frame;
+
+ old_frame = priv->current_frame;
+ new_frame = clutter_gst_frame_new (pipeline);
+ priv->current_frame = new_frame;
+
+ new_frame->resolution.par_n = old_frame->resolution.par_n;
+ new_frame->resolution.par_d = old_frame->resolution.par_d;
+
+ if (new_frame->resolution.width != old_frame->resolution.width ||
+ new_frame->resolution.height != old_frame->resolution.height)
+ {
+ g_signal_emit (self, signals[SIZE_CHANGE], 0,
+ new_frame->resolution.width,
+ new_frame->resolution.height);
+ }
+ if (old_frame)
+ g_boxed_free (CLUTTER_GST_TYPE_FRAME, old_frame);
+}
+
+static void
+_new_frame_from_pipeline (CoglGstVideoSink *sink,
+ ClutterGstContent *self)
+{
+ update_frame (self, cogl_gst_video_sink_get_pipeline (sink));
+
+ clutter_content_invalidate (CLUTTER_CONTENT (self));
+}
+
+static void
+_pixel_aspect_ratio_changed (CoglGstVideoSink *sink,
+ GParamSpec *pspec,
+ ClutterGstContent *self)
+{
+ clutter_gst_frame_update_pixel_aspect_ratio (self->priv->current_frame,
+ sink);
+}
+
+static void
+content_set_sink (ClutterGstContent *self,
+ CoglGstVideoSink *sink)
+{
+ ClutterGstContentPrivate *priv = self->priv;
+
+ if (priv->sink == sink)
+ return;
+
+ if (priv->sink)
+ {
+ g_signal_handlers_disconnect_by_func (priv->sink,
+ _new_frame_from_pipeline, self);
+ g_signal_handlers_disconnect_by_func (priv->sink,
+ _pixel_aspect_ratio_changed, self);
+ g_clear_object (&priv->sink);
+ }
+
+ if (sink)
+ {
+ CoglPipeline *pipeline;
+
+ priv->sink = g_object_ref_sink (sink);
+ g_signal_connect (priv->sink, "new-frame",
+ G_CALLBACK (_new_frame_from_pipeline), self);
+ g_signal_connect (priv->sink, "notify::pixel-aspect-ratio",
+ G_CALLBACK (_pixel_aspect_ratio_changed), self);
+
+ pipeline = cogl_gst_video_sink_get_pipeline (priv->sink);
+ if (pipeline)
+ update_frame (self, pipeline);
+ }
+
+ g_object_notify (G_OBJECT (self), "video-sink");
+}
+
+static gboolean
+clutter_gst_content_get_preferred_size (ClutterContent *content,
+ gfloat *width,
+ gfloat *height)
+{
+ ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (content)->priv;
+
+ if (!priv->current_frame)
+ return FALSE;
+
+ if (width)
+ *width = priv->current_frame->resolution.width;
+ if (height)
+ *height = priv->current_frame->resolution.height;
+
+ return TRUE;
+}
+
+static void
+clutter_gst_content_paint_content (ClutterContent *content,
+ ClutterActor *actor,
+ ClutterPaintNode *root)
+{
+ ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (content)->priv;
+ ClutterActorBox box;
+ ClutterPaintNode *node;
+ ClutterContentRepeat repeat;
+ guint8 paint_opacity;
+
+ if (!priv->current_frame)
+ return;
+
+ clutter_actor_get_content_box (actor, &box);
+ paint_opacity = clutter_actor_get_paint_opacity (actor);
+ repeat = clutter_actor_get_content_repeat (actor);
+
+
+ cogl_pipeline_set_color4ub (priv->current_frame->pipeline,
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ node = clutter_pipeline_node_new (priv->current_frame->pipeline);
+ clutter_paint_node_set_name (node, "Video");
+
+ if (repeat == CLUTTER_REPEAT_NONE)
+ clutter_paint_node_add_rectangle (node, &box);
+ else
+ {
+ float t_w = 1.f, t_h = 1.f;
+
+ if ((repeat & CLUTTER_REPEAT_X_AXIS) != FALSE)
+ t_w = (box.x2 - box.x1) / priv->current_frame->resolution.width;
+
+ if ((repeat & CLUTTER_REPEAT_Y_AXIS) != FALSE)
+ t_h = (box.y2 - box.y1) / priv->current_frame->resolution.height;
+
+ clutter_paint_node_add_texture_rectangle (node, &box,
+ 0.f, 0.f,
+ t_w, t_h);
+ }
+
+ clutter_paint_node_add_child (root, node);
+ clutter_paint_node_unref (node);
+}
+
+static void
+clutter_content_iface_init (ClutterContentIface *iface)
+{
+ iface->get_preferred_size = clutter_gst_content_get_preferred_size;
+ iface->paint_content = clutter_gst_content_paint_content;
+}
+
+static void
+clutter_gst_content_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterGstContent *self = CLUTTER_GST_CONTENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIDEO_SINK:
+ content_set_sink (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_gst_content_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (object)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_VIDEO_SINK:
+ g_value_set_object (value, priv->sink);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_gst_content_dispose (GObject *object)
+{
+ ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (object)->priv;
+
+ g_clear_object (&priv->sink);
+
+ if (priv->current_frame)
+ {
+ g_boxed_free (CLUTTER_GST_TYPE_FRAME, priv->current_frame);
+ priv->current_frame = NULL;
+ }
+
+ G_OBJECT_CLASS (clutter_gst_content_parent_class)->dispose (object);
+}
+
+static void
+clutter_gst_content_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (clutter_gst_content_parent_class)->finalize (object);
+}
+
+static void
+clutter_gst_content_class_init (ClutterGstContentClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->set_property = clutter_gst_content_set_property;
+ gobject_class->get_property = clutter_gst_content_get_property;
+ gobject_class->dispose = clutter_gst_content_dispose;
+ gobject_class->finalize = clutter_gst_content_finalize;
+
+ g_type_class_add_private (klass, sizeof (ClutterGstContentPrivate));
+
+ props[PROP_VIDEO_SINK] =
+ g_param_spec_object ("video-sink",
+ "video-sink",
+ "video-sink",
+ COGL_GST_TYPE_VIDEO_SINK,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+ g_object_class_install_properties (gobject_class, PROP_LAST, props);
+
+
+ /**
+ * ClutterGstContent::size-change:
+ * @content: the #ClutterGstContent instance that received the signal
+ * @width: new width of the frames
+ * @height: new height of the frames
+ *
+ * The ::size-change signal is emitted each time the video size changes.
+ */
+ signals[SIZE_CHANGE] =
+ g_signal_new ("size-change",
+ CLUTTER_GST_TYPE_CONTENT,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _clutter_gst_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT, G_TYPE_INT);
+}
+
+
+static void
+clutter_gst_content_init (ClutterGstContent *self)
+{
+ ClutterGstContentPrivate *priv;
+
+ self->priv = priv = CLUTTER_GST_CONTENT_GET_PRIVATE (self);
+ priv->sink = NULL;
+ priv->current_frame = clutter_gst_create_blank_frame (NULL);
+}
+
+
+/**
+ * clutter_gst_content_new:
+ *
+ * Returns: (transfer full): a new #ClutterGstContent instance
+ */
+ClutterContent *
+clutter_gst_content_new (void)
+{
+ CoglGstVideoSink *sink;
+
+ sink = cogl_gst_video_sink_new (clutter_gst_get_cogl_context ());
+ return g_object_new (CLUTTER_GST_TYPE_CONTENT,
+ "video-sink", sink,
+ NULL);
+}
+
+/**
+ * clutter_gst_content_new_with_sink:
+ *
+ * Returns: (transfer full): a new #ClutterGstContent instance
+ */
+ClutterContent *
+clutter_gst_content_new_with_sink (CoglGstVideoSink *sink)
+{
+ return g_object_new (CLUTTER_GST_TYPE_CONTENT,
+ "video-sink", sink,
+ NULL);
+}
+
+
+/**
+ * clutter_gst_content_set_sink:
+ * @self: A #ClutterGstContent
+ * @sink: A #CoglGstVideoSink or %NULL
+ */
+void
+clutter_gst_content_set_sink (ClutterGstContent *self,
+ CoglGstVideoSink *sink)
+{
+ g_return_if_fail (CLUTTER_GST_IS_CONTENT (self));
+ g_return_if_fail (sink == NULL || COGL_GST_IS_VIDEO_SINK (sink));
+
+ content_set_sink (self, sink);
+}
+
+/**
+ * clutter_gst_content_get_sink:
+ * @self: A #ClutterGstContent
+ *
+ * Returns: (transfer none):
+ */
+CoglGstVideoSink *
+clutter_gst_content_get_sink (ClutterGstContent *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CONTENT (self), NULL);
+
+ return self->priv->sink;
+}
diff --git a/clutter-gst/clutter-gst-content.h b/clutter-gst/clutter-gst-content.h
new file mode 100644
index 0000000..15d1337
--- /dev/null
+++ b/clutter-gst/clutter-gst-content.h
@@ -0,0 +1,91 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * Copyright (C) 2013 Bastian Winkler <buz@netbuz.org>
+ *
+ * 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 __CLUTTER_GST_CONTENT_H__
+#define __CLUTTER_GST_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <cogl-gst/cogl-gst.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+
+#define CLUTTER_GST_TYPE_CONTENT (clutter_gst_content_get_type())
+#define CLUTTER_GST_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_GST_TYPE_CONTENT, ClutterGstContent))
+#define CLUTTER_GST_IS_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_GST_TYPE_CONTENT))
+#define CLUTTER_GST_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_GST_TYPE_CONTENT, ClutterGstContentClass))
+#define CLUTTER_GST_IS_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_GST_TYPE_CONTENT))
+#define CLUTTER_GST_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_GST_TYPE_CONTENT, ClutterGstContentClass))
+
+
+typedef struct _ClutterGstContent ClutterGstContent;
+typedef struct _ClutterGstContentPrivate ClutterGstContentPrivate;
+typedef struct _ClutterGstContentClass ClutterGstContentClass;
+
+
+/**
+ * ClutterGstContent:
+ *
+ * The #ClutterGstContent structure contains only private data
+ * and should be accessed using the provided API
+ *
+ * Since: 0.0
+ */
+struct _ClutterGstContent
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ ClutterGstContentPrivate *priv;
+};
+
+/**
+ * ClutterGstContentClass:
+ *
+ * The #ClutterGstContentClass structure contains only private data
+ * and should be accessed using the provided API
+ *
+ * Since: 0.0
+ */
+struct _ClutterGstContentClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+};
+
+GType clutter_gst_content_get_type (void) G_GNUC_CONST;
+
+ClutterContent * clutter_gst_content_new (void);
+
+ClutterContent * clutter_gst_content_new_with_sink (CoglGstVideoSink *sink);
+
+void clutter_gst_content_set_sink (ClutterGstContent *self,
+ CoglGstVideoSink *sink);
+
+CoglGstVideoSink * clutter_gst_content_get_sink (ClutterGstContent *self);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_GST_CONTENT_H__ */
diff --git a/clutter-gst/clutter-gst.h b/clutter-gst/clutter-gst.h
index b517766..be2aadc 100644
--- a/clutter-gst/clutter-gst.h
+++ b/clutter-gst/clutter-gst.h
@@ -36,6 +36,7 @@
#include "clutter-gst-aspectratio.h"
#include "clutter-gst-camera-device.h"
#include "clutter-gst-camera.h"
+#include "clutter-gst-content.h"
#include "clutter-gst-crop.h"
#include "clutter-gst-pipeline.h"
#include "clutter-gst-playback.h"
diff --git a/examples/.gitignore b/examples/.gitignore
index c71f574..9fe58de 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -1,4 +1,5 @@
camera-player
+video-content
video-player
video-sink
video-sink-navigation
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 0052d75..f8117ff 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,6 +1,6 @@
NULL = #
-noinst_PROGRAMS = camera-player video-player video-sink video-sink-navigation
+noinst_PROGRAMS = camera-player video-player video-sink video-sink-navigation video-content
INCLUDES = -I$(top_srcdir) \
$(MAINTAINER_CFLAGS) \
@@ -34,6 +34,13 @@ video_sink_navigation_LDFLAGS = \
$(GST_LIBS) \
$(top_builddir)/clutter-gst/libclutter-gst-@CLUTTER_GST_MAJORMINOR@.la
+video_content_SOURCES = video-content.c
+video_content_CFLAGS = $(CLUTTER_GST_CFLAGS) $(GST_CFLAGS)
+video_content_LDFLAGS = \
+ $(CLUTTER_GST_LIBS) \
+ $(GST_LIBS) \
+ $(top_builddir)/clutter-gst/libclutter-gst-@CLUTTER_GST_MAJORMINOR@.la
+
EXTRA_DIST = \
media-actions-pause.png \
media-actions-start.png \
diff --git a/examples/video-content.c b/examples/video-content.c
new file mode 100644
index 0000000..c7368cc
--- /dev/null
+++ b/examples/video-content.c
@@ -0,0 +1,172 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * Copyright (C) 2013 Bastian Winkler <buz@netbuz.org>
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <cogl-gst/cogl-gst.h>
+#include <clutter-gst/clutter-gst.h>
+
+
+static const struct {
+ ClutterContentGravity gravity;
+ const char *name;
+} gravities[] = {
+ { CLUTTER_CONTENT_GRAVITY_TOP_LEFT, "Top Left" },
+ { CLUTTER_CONTENT_GRAVITY_TOP, "Top" },
+ { CLUTTER_CONTENT_GRAVITY_TOP_RIGHT, "Top Right" },
+ { CLUTTER_CONTENT_GRAVITY_LEFT, "Left" },
+ { CLUTTER_CONTENT_GRAVITY_CENTER, "Center" },
+ { CLUTTER_CONTENT_GRAVITY_RIGHT, "Right" },
+ { CLUTTER_CONTENT_GRAVITY_BOTTOM_LEFT, "Bottom Left" },
+ { CLUTTER_CONTENT_GRAVITY_BOTTOM, "Bottom" },
+ { CLUTTER_CONTENT_GRAVITY_BOTTOM_RIGHT, "Bottom Right" },
+ { CLUTTER_CONTENT_GRAVITY_RESIZE_FILL, "Resize Fill" },
+ { CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT, "Resize Aspect" },
+};
+
+static int n_gravities = G_N_ELEMENTS (gravities);
+static int cur_gravity = 0;
+
+static const struct {
+ ClutterContentRepeat repeat;
+ const gchar *name;
+} repeats[] = {
+ { CLUTTER_REPEAT_NONE, "None" },
+ { CLUTTER_REPEAT_X_AXIS, "X-Axis" },
+ { CLUTTER_REPEAT_Y_AXIS, "Y-Axis" },
+ { CLUTTER_REPEAT_BOTH, "Both" },
+};
+
+static int n_repeats = G_N_ELEMENTS (repeats);
+static int cur_repeat = 0;
+
+
+static GstElement *pipeline = NULL;
+
+static gboolean
+on_key_press (ClutterActor *stage,
+ ClutterEvent *event,
+ ClutterActor *actor)
+{
+
+ switch (clutter_event_get_key_symbol (event))
+ {
+ case CLUTTER_KEY_r:
+ clutter_actor_set_content_repeat (actor, repeats[cur_repeat].repeat);
+ g_print ("Content repeat: %s\n", repeats[cur_repeat].name);
+ cur_repeat += 1;
+ if (cur_repeat >= n_repeats)
+ cur_repeat = 0;
+ break;
+
+ case CLUTTER_KEY_q:
+ clutter_main_quit ();
+ break;
+
+ case CLUTTER_KEY_g:
+ clutter_actor_save_easing_state (actor);
+ clutter_actor_set_content_gravity (actor, gravities[cur_gravity].gravity);
+ clutter_actor_restore_easing_state (actor);
+ g_print ("Content gravity: %s\n", gravities[cur_gravity].name);
+ cur_gravity += 1;
+
+ if (cur_gravity >= n_gravities)
+ cur_gravity = 0;
+ break;
+
+ case CLUTTER_KEY_Left:
+ {
+ gint64 pos, dur;
+
+ if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur))
+ break;
+ if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos))
+ break;
+
+ gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH,
+ CLAMP (pos - GST_SECOND * 10, 0, dur));
+ break;
+ }
+
+ case CLUTTER_KEY_Right:
+ {
+ gint64 pos, dur;
+
+ if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur))
+ break;
+ if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos))
+ break;
+
+ gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH,
+ CLAMP (pos + GST_SECOND * 10, 0, dur));
+ break;
+ }
+
+ default:
+ return CLUTTER_EVENT_PROPAGATE;
+ }
+
+ return CLUTTER_EVENT_STOP;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ ClutterActor *stage, *actor;
+ ClutterContent *video;
+ CoglGstVideoSink *video_sink;
+
+ if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
+ return EXIT_FAILURE;
+ gst_init (&argc, &argv);
+
+ stage = clutter_stage_new ();
+ g_signal_connect (stage, "destroy",
+ G_CALLBACK (clutter_main_quit), NULL);
+ clutter_stage_set_fullscreen (CLUTTER_STAGE (stage), TRUE);
+
+ video = clutter_gst_content_new ();
+ video_sink = clutter_gst_content_get_sink (CLUTTER_GST_CONTENT (video));
+
+ actor = clutter_actor_new ();
+ clutter_actor_set_reactive (actor, TRUE);
+ clutter_actor_set_background_color (actor, CLUTTER_COLOR_Black);
+ clutter_actor_add_constraint (actor, clutter_bind_constraint_new (stage, CLUTTER_BIND_SIZE, 0.f));
+ clutter_actor_set_content_gravity (actor, gravities[n_gravities - 1].gravity);
+ clutter_actor_set_content (actor, video);
+ clutter_actor_add_child (stage, actor);
+
+ g_signal_connect (stage, "key-press-event",
+ G_CALLBACK (on_key_press), actor);
+
+ pipeline = gst_element_factory_make ("playbin", NULL);
+ g_object_set (pipeline, "uri", argv[1], "video-sink", video_sink, NULL);
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ clutter_actor_show (stage);
+ clutter_main ();
+
+ return 0;
+}