demos: Round up the paintable demos with a media stream
authorBenjamin Otte <otte@redhat.com>
Tue, 27 Feb 2018 02:58:52 +0000 (03:58 +0100)
committerBenjamin Otte <otte@redhat.com>
Sun, 18 Mar 2018 20:01:23 +0000 (21:01 +0100)
demos/gtk-demo/demo.gresource.xml
demos/gtk-demo/meson.build
demos/gtk-demo/paintable.h
demos/gtk-demo/paintable_mediastream.c [new file with mode: 0644]

index 81519f1c371bca31dd7ccfdb3499ad5a95b935a6..ccc17fe9ad62368d0065f3a24395af606ed3ddde 100644 (file)
     <file>pagesetup.c</file>
     <file>paintable.c</file>
     <file>paintable_animated.c</file>
+    <file>paintable_mediastream.c</file>
     <file>panes.c</file>
     <file>pickers.c</file>
     <file>pixbufs.c</file>
index 5d3a318bd15d0a45d4c0874be784e7826636060b..0ad157bf8de57908de3b85c9f3d7806c4cde23a4 100644 (file)
@@ -47,6 +47,7 @@ demos = files([
   'overlay2.c',
   'paintable.c',
   'paintable_animated.c',
+  'paintable_mediastream.c',
   'panes.c',
   'pickers.c',
   'pixbufs.c',
index 0f561b271376773ec7d514ae60774570ed6f67e9..33bc4dbc8b24b5dad668e0213c2a9ee765397925 100644 (file)
@@ -10,5 +10,6 @@ void            gtk_nuclear_snapshot           (GtkSnapshot     *snapshot,
 
 GdkPaintable *  gtk_nuclear_icon_new            (double          rotation);
 GdkPaintable *  gtk_nuclear_animation_new       (void);
+GtkMediaStream *gtk_nuclear_media_stream_new    (void);
 
 #endif /* __PAINTABLE_H__ */
diff --git a/demos/gtk-demo/paintable_mediastream.c b/demos/gtk-demo/paintable_mediastream.c
new file mode 100644 (file)
index 0000000..b06cf11
--- /dev/null
@@ -0,0 +1,312 @@
+/* Paintable/A media stream
+ *
+ * GdkPaintable is also used by the GtkMediaStream class.
+ *
+ * This demo code turns the nuclear media_stream into the object
+ * GTK uses for videos. This allows treating the icon like a
+ * regular video, so we can for example attach controls to it.
+ *
+ * After all, what good is an media_stream if one cannot pause
+ * it.
+ */
+
+#include <gtk/gtk.h>
+
+#include "paintable.h"
+
+static GtkWidget *window = NULL;
+
+/* First, add the boilerplate for the object itself.
+ * This part would normally go in the header.
+ */
+#define GTK_TYPE_NUCLEAR_MEDIA_STREAM (gtk_nuclear_media_stream_get_type ())
+G_DECLARE_FINAL_TYPE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK, NUCLEAR_MEDIA_STREAM, GtkMediaStream)
+
+/* Do a full rotation in 5 seconds.
+ *
+ * We do not save steps here but real timestamps.
+ * GtkMediaStream uses microseconds, so we will do so, too.
+ */
+#define DURATION (5 * G_USEC_PER_SEC)
+
+/* Declare the struct. */
+struct _GtkNuclearMediaStream
+{
+  /* We now inherit from the media stream object. */
+  GtkMediaStream parent_instance;
+
+  /* This variable stores the progress of our video.
+   */
+  gint64 progress;
+
+  /* This variable stores the timestamp of the last
+   * time we updated the progress variable when the
+   * video is currently playing.
+   * This is so that we can always accurately compute the
+   * progress we've had, even if the timeout does not
+   * exactly work.
+   */
+  gint64 last_time;
+
+  /* This variable again holds the ID of the timer that 
+   * updates our progress variable. Nothing changes about
+   * how this works compared to the previous example.
+   */
+  guint source_id;
+};
+
+struct _GtkNuclearMediaStreamClass
+{
+  GObjectClass parent_class;
+};
+
+/* GtkMediaStream is a GdkPaintable. So when we want to display video,
+ * we have to implement the interface, just like in the animation example.
+ */
+static void
+gtk_nuclear_media_stream_snapshot (GdkPaintable *paintable,
+                                   GdkSnapshot  *snapshot,
+                                   double        width,
+                                   double        height)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
+
+  /* We call the function from the previous example here. */
+  gtk_nuclear_snapshot (snapshot,
+                        width, height,
+                        2 * G_PI * nuclear->progress / DURATION);
+}
+
+static GdkPaintable *
+gtk_nuclear_media_stream_get_current_image (GdkPaintable *paintable)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
+
+  /* Same thing as with the animation */
+  return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / DURATION);
+}
+
+static GdkPaintableFlags
+gtk_nuclear_media_stream_get_flags (GdkPaintable *paintable)
+{
+  /* And same thing as with the animation over here, too. */
+  return GDK_PAINTABLE_STATIC_SIZE;
+}
+
+static void
+gtk_nuclear_media_stream_paintable_init (GdkPaintableInterface *iface)
+{
+  iface->snapshot = gtk_nuclear_media_stream_snapshot;
+  iface->get_current_image = gtk_nuclear_media_stream_get_current_image;
+  iface->get_flags = gtk_nuclear_media_stream_get_flags;
+}
+
+/* This time, we inherit from GTK_TYPE_MEDIA_STREAM */
+G_DEFINE_TYPE_WITH_CODE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK_TYPE_MEDIA_STREAM,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                                gtk_nuclear_media_stream_paintable_init))
+
+static gboolean
+gtk_nuclear_media_stream_step (gpointer data)
+{
+  GtkNuclearMediaStream *nuclear = data;
+  gint64 current_time;
+
+  /* Compute the time that has elapsed since the last time we were called
+   * and add it to our current progress.
+   */
+  current_time = g_source_get_time (g_main_current_source ());
+  nuclear->progress += current_time - nuclear->last_time;
+
+  /* Check if we've ended */
+  if (nuclear->progress > DURATION)
+    {
+      if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (nuclear)))
+        {
+          /* We're looping. So make the progress loop using modulo */
+          nuclear->progress %= DURATION;
+        }
+      else
+        {
+          /* Just make sure we don't overflow */
+          nuclear->progress = DURATION;
+        }
+    }
+
+  /* Update the last time to the current timestamp. */
+  nuclear->last_time = current_time;
+
+  /* Update the timestamp of the media stream */
+  gtk_media_stream_update (GTK_MEDIA_STREAM (nuclear), nuclear->progress);
+
+  /* We also need to invalidate our contents again.
+   * After all, we are a video and not just an audio stream.
+   */
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
+
+  /* Now check if we have finished playing and if so,
+   * tell the media stream. The media stream will then
+   * call our pause function to pause the stream.
+   */
+  if (nuclear->progress >= DURATION)
+    gtk_media_stream_ended (GTK_MEDIA_STREAM (nuclear));
+
+  /* The timeout function is removed by the pause function,
+   * so we can just always return this value.
+   */
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+gtk_nuclear_media_stream_play (GtkMediaStream *stream)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
+
+  /* If we're already at the end of the stream, we don't want
+   * to start playing and exit early.
+   */
+  if (nuclear->progress >= DURATION)
+    return FALSE;
+
+  /* This time, we add the source only when we start playing.
+   */
+  nuclear->source_id = g_timeout_add (10,
+                                      gtk_nuclear_media_stream_step,
+                                      nuclear);
+
+  /* We also want to initialize our time, so that we can
+   * do accurate updates.
+   */
+  nuclear->last_time = g_get_monotonic_time ();
+  
+  /* We successfully started playing, so we return TRUE here. */
+  return TRUE;
+}
+
+static void
+gtk_nuclear_media_stream_pause (GtkMediaStream *stream)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
+
+  /* This function will be called when a playing stream
+   * gets paused.
+   * So we remove the updating source here and set it
+   * back to 0 so that the finalize function doesn't try
+   * to remove it again.
+   */
+  g_source_remove (nuclear->source_id);
+  nuclear->source_id = 0;
+  nuclear->last_time = 0;
+}
+
+static void
+gtk_nuclear_media_stream_seek (GtkMediaStream *stream,
+                               gint64          timestamp)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
+
+  /* This is optional functionality for media streams,
+   * but not being able to seek is kinda boring.
+   * And it's trivial to implement, so let's go for it.
+   */
+  nuclear->progress = timestamp;
+
+  /* Media streams are asynchronous, so seeking can take a while.
+   * We however don't need that functionality, so we can just
+   * report success.
+   */
+  gtk_media_stream_seek_success (stream);
+
+  /* We also have to update our timestamp and tell the
+   * paintable interface abbout the seek
+   */
+  gtk_media_stream_update (stream, nuclear->progress);
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
+}
+
+/* Again, we need to implement the finalize function.
+ */
+static void
+gtk_nuclear_media_stream_finalize (GObject *object)
+{
+  GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (object);
+
+  /* This time, we need to check if the source exists before
+   * removing it as it only exists while we are playing.
+   */
+  if (nuclear->source_id > 0)
+    g_source_remove (nuclear->source_id);
+
+  /* Don't forget to chain up to the parent class' implementation
+   * of the finalize function.
+   */
+  G_OBJECT_CLASS (gtk_nuclear_media_stream_parent_class)->finalize (object);
+}
+
+/* In the class declaration, we need to implement the media stream */
+static void
+gtk_nuclear_media_stream_class_init (GtkNuclearMediaStreamClass *klass)
+{
+  GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  stream_class->play = gtk_nuclear_media_stream_play;
+  stream_class->pause = gtk_nuclear_media_stream_pause;
+  stream_class->seek = gtk_nuclear_media_stream_seek;
+
+  gobject_class->finalize = gtk_nuclear_media_stream_finalize;
+}
+
+static void
+gtk_nuclear_media_stream_init (GtkNuclearMediaStream *nuclear)
+{
+  /* This time, we don't have to add a timer here, because media
+   * streams start paused.
+   *
+   * However, media streams need to tell GTK once they are intialized,
+   * so we do that here.
+   */
+  gtk_media_stream_prepared (GTK_MEDIA_STREAM (nuclear),
+                             FALSE,
+                             TRUE,
+                             TRUE,
+                             DURATION);
+}
+
+/* And finally, we add the simple constructor we declared in the header. */
+GtkMediaStream *
+gtk_nuclear_media_stream_new (void)
+{
+  return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL);
+}
+
+GtkWidget *
+do_paintable_mediastream (GtkWidget *do_widget)
+{
+  GtkMediaStream *nuclear;
+  GtkWidget *video;
+
+  if (!window)
+    {
+      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+      gtk_window_set_display (GTK_WINDOW (window),
+                              gtk_widget_get_display (do_widget));
+      gtk_window_set_title (GTK_WINDOW (window), "Nuclear MediaStream");
+      gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
+
+      nuclear = gtk_nuclear_media_stream_new ();
+      gtk_media_stream_set_loop (GTK_MEDIA_STREAM (nuclear), TRUE);
+
+      video = gtk_video_new_for_media_stream (nuclear);
+      gtk_container_add (GTK_CONTAINER (window), video);
+
+      g_object_unref (nuclear);
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_show (window);
+  else
+    gtk_widget_destroy (window);
+
+  return window;
+}