[gtk/wip/otte/paintable: 1/8] demos: Add a video playing GdkPaintable



commit 11e0f1bbf46dddfacdccbad5512f0f20a887dd3a
Author: Benjamin Otte <otte redhat com>
Date:   Sat Feb 17 07:38:11 2018 +0100

    demos: Add a video playing GdkPaintable
    
    This is using ffmpeg, so it can't be included in GTK proper (because
    FFmpeg is GPL). But it's very useful for feature-testing and
    performance-benchmarking to have a video player available.
    
    Also, it might inspire people to actually improve the situation of video
    in GTK, seeing as this is just a few 100 lines of C.
    
    The code is very rudimentary and barely good enough to do anything
    useful, but it gets the job done.

 config.h.meson                |   3 +
 demos/gtk-demo/gtkdemovideo.c | 569 ++++++++++++++++++++++++++++++++++++++++++
 demos/gtk-demo/gtkdemovideo.h |  41 +++
 demos/gtk-demo/images.c       |  43 +++-
 demos/gtk-demo/meson.build    |   6 +-
 demos/gtk-demo/widgetbowl.c   |  11 +
 meson.build                   |  11 +
 meson_options.txt             |   2 +
 8 files changed, 680 insertions(+), 6 deletions(-)
---
diff --git a/config.h.meson b/config.h.meson
index 54dec7fa82..054831bbfa 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -41,6 +41,9 @@
 /* Define to 1 if you have the <dlfcn.h> header file. */
 #mesondefine HAVE_DLFCN_H
 
+/* Have the ffmpeg library */
+#mesondefine HAVE_FFMPEG
+
 /* Define to 1 if you have the <ftw.h> header file. */
 #mesondefine HAVE_FTW_H
 
diff --git a/demos/gtk-demo/gtkdemovideo.c b/demos/gtk-demo/gtkdemovideo.c
new file mode 100644
index 0000000000..0d511368f2
--- /dev/null
+++ b/demos/gtk-demo/gtkdemovideo.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkdemovideo.h"
+
+#ifdef HAVE_FFMPEG
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#endif
+
+enum {
+  PROP_0,
+  PROP_FILENAME,
+  PROP_PLAYING,
+
+  N_PROPS
+};
+
+struct _GtkDemoVideo {
+  GObject parent_instance;
+
+  char *filename;
+
+#ifdef HAVE_FFMPEG
+  AVFormatContext *format_ctx;
+  AVCodecContext *codec_ctx;
+  int stream_id;
+  struct SwsContext *sws_ctx;
+#endif
+  GdkTexture *current_texture;
+  gint64 current_texture_duration;
+
+  gint64 timestamp; /* monotonic time when we displayed the last frame */
+  guint next_frame_cb; /* Source ID of next frame callback */
+  gboolean playing;
+};
+
+struct _GtkDemoVideoClass {
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+gtk_demo_video_paintable_snapshot (GdkPaintable *paintable,
+                                   GdkSnapshot  *snapshot,
+                                   double        width,
+                                   double        height)
+{
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (paintable);
+
+  if (video->current_texture)
+    {
+      gdk_paintable_snapshot (GDK_PAINTABLE (video->current_texture), snapshot, width, height);
+    }
+  else
+    {
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA) { 1.0, 0.1, 0.6, 1.0 },
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height),
+                                 "Video Fallback image");
+    }
+}
+
+static GdkPaintable *
+gtk_demo_video_paintable_get_current_image (GdkPaintable *paintable)
+{
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (paintable);
+
+  return GDK_PAINTABLE (g_object_ref (video->current_texture));
+}
+
+static int
+gtk_demo_video_paintable_get_intrinsic_width (GdkPaintable *paintable)
+{
+#ifdef HAVE_FFMPEG
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (paintable);
+
+  if (video->codec_ctx)
+    return video->codec_ctx->width;
+#endif
+
+  return 0;
+}
+
+static int
+gtk_demo_video_paintable_get_intrinsic_height (GdkPaintable *paintable)
+{
+#ifdef HAVE_FFMPEG
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (paintable);
+
+  if (video->codec_ctx)
+    return video->codec_ctx->height;
+#endif
+
+  return 0;
+}
+
+static double gtk_demo_video_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
+{
+#ifdef HAVE_FFMPEG
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (paintable);
+
+  if (video->codec_ctx)
+    return (double) video->codec_ctx->width / video->codec_ctx->height;
+#endif
+
+  return 0.0;
+};
+
+static void
+gtk_demo_video_paintable_init (GdkPaintableInterface *iface)
+{
+  iface->snapshot = gtk_demo_video_paintable_snapshot;
+  iface->get_current_image = gtk_demo_video_paintable_get_current_image;
+  iface->get_intrinsic_width = gtk_demo_video_paintable_get_intrinsic_width;
+  iface->get_intrinsic_height = gtk_demo_video_paintable_get_intrinsic_height;
+  iface->get_intrinsic_aspect_ratio = gtk_demo_video_paintable_get_intrinsic_aspect_ratio;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkDemoVideo, gtk_demo_video, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                                gtk_demo_video_paintable_init))
+
+static void
+gtk_demo_video_set_property (GObject      *gobject,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_FILENAME:
+      gtk_demo_video_set_filename (video, g_value_get_string (value));
+      break;
+
+    case PROP_PLAYING:
+      gtk_demo_video_set_playing (video, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_demo_video_get_property (GObject    *gobject,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_FILENAME:
+      g_value_set_string (value, video->filename);
+      break;
+
+    case PROP_PLAYING:
+      g_value_set_boolean (value, video->playing);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_demo_video_stop (GtkDemoVideo *video)
+{
+  if (!video->playing)
+    return;
+
+  if (video->next_frame_cb)
+    {
+      g_source_remove (video->next_frame_cb);
+      video->next_frame_cb = 0;
+    }
+
+  video->timestamp = FALSE;
+  video->playing = FALSE;
+  g_object_notify_by_pspec (G_OBJECT (video), properties[PROP_PLAYING]);
+}
+
+static void 
+gtk_demo_video_clear (GtkDemoVideo *video)
+{
+  g_clear_pointer (&video->filename, g_free);
+
+#ifdef HAVE_FFMPEG
+  g_clear_pointer (&video->sws_ctx, sws_freeContext);
+  g_clear_pointer (&video->codec_ctx, avcodec_close);
+  avformat_close_input (&video->format_ctx);
+  video->stream_id = -1;
+#endif
+  g_clear_object (&video->current_texture);
+
+  gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
+}
+
+static void
+gtk_demo_video_dispose (GObject *object)
+{
+  GtkDemoVideo *video = GTK_DEMO_VIDEO (object);
+
+  gtk_demo_video_stop (video);
+  gtk_demo_video_clear (video);
+
+  G_OBJECT_CLASS (gtk_demo_video_parent_class)->dispose (object);
+}
+
+static void
+gtk_demo_video_class_init (GtkDemoVideoClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = gtk_demo_video_set_property;
+  gobject_class->get_property = gtk_demo_video_get_property;
+  gobject_class->dispose = gtk_demo_video_dispose;
+
+  properties[PROP_FILENAME] =
+    g_param_spec_string ("filename",
+                         "Filename",
+                         "The file to play",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         G_PARAM_EXPLICIT_NOTIFY);
+
+  properties[PROP_PLAYING] =
+    g_param_spec_string ("playing",
+                         "Playing",
+                         "TRUE if the file is playing",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+#ifdef HAVE_FFMPEG
+  av_register_all ();
+#endif
+}
+
+static void
+gtk_demo_video_init (GtkDemoVideo *video)
+{
+#ifdef HAVE_FFMPEG
+  video->stream_id = -1;
+#endif
+}
+
+GtkDemoVideo *
+gtk_demo_video_new (void)
+{
+  return g_object_new (GTK_TYPE_DEMO_VIDEO, NULL);
+}
+
+GtkDemoVideo *
+gtk_demo_video_new_for_filename (const char *filename)
+{
+  GtkDemoVideo *video;
+
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  video = gtk_demo_video_new ();
+  gtk_demo_video_set_filename (video, filename);
+  gtk_demo_video_set_playing (video, TRUE);
+
+  return video;
+}
+
+static void
+gtk_demo_video_set_error (GtkDemoVideo *video,
+                          const char   *error_message)
+{
+}
+
+#ifdef HAVE_FFMPEG
+static void
+gtk_demo_video_set_ffmpeg_error (GtkDemoVideo *video,
+                                 int           av_errnum)
+{
+  char s[AV_ERROR_MAX_STRING_SIZE];
+
+  if (av_strerror (av_errnum, s, sizeof (s) != 0))
+    snprintf (s, sizeof (s), "Unspecified error decoding video");
+
+  gtk_demo_video_set_error (video, s);
+}
+
+static GdkTexture *
+gtk_demo_video_decode_frame (GtkDemoVideo *video,
+                             gint64       *duration)
+{
+  GdkTexture *texture;
+  AVPacket packet;
+  AVFrame *frame;
+  int errnum;
+  guchar *data;
+
+  frame = av_frame_alloc ();
+
+  for (errnum = av_read_frame (video->format_ctx, &packet);
+       errnum >= 0;
+       errnum = av_read_frame (video->format_ctx, &packet))
+    {
+      if (packet.stream_index == video->stream_id)
+        {
+          errnum = avcodec_send_packet (video->codec_ctx, &packet);
+          if (errnum >= 0)
+            {
+              errnum = avcodec_receive_frame (video->codec_ctx, frame);
+              if (errnum >= 0) 
+                {
+                  av_packet_unref (&packet);
+                  break;
+                }
+            }
+        }
+
+      av_packet_unref (&packet);
+    }
+
+  if (errnum < 0)
+    {
+      gtk_demo_video_set_ffmpeg_error (video, errnum);
+      av_frame_free (&frame);
+      *duration = 0;
+      return NULL;
+    }
+  
+  data = g_try_malloc0 (video->codec_ctx->width * video->codec_ctx->height * 4);
+  if (data == NULL)
+    {
+      gtk_demo_video_set_error (video, "Not enough memory");
+      av_frame_free (&frame);
+      *duration = 0;
+      return NULL;
+    }
+
+  sws_scale(video->sws_ctx,
+            (const uint8_t * const *) frame->data, frame->linesize,
+            0, video->codec_ctx->height,
+            (uint8_t *[1]) { data }, (int[1]) { video->codec_ctx->width * 4 });
+  texture = gdk_texture_new_for_data (data,
+                                      video->codec_ctx->width,
+                                      video->codec_ctx->height,
+                                      video->codec_ctx->width * 4);
+  g_free (data);
+
+  if (av_frame_get_pkt_duration (frame))
+    *duration = av_rescale_q (av_frame_get_pkt_duration (frame),
+                              video->format_ctx->streams[video->stream_id]->time_base,
+                              (AVRational) { 1, G_USEC_PER_SEC });
+  else
+    *duration = av_rescale_q (1,
+                              video->format_ctx->streams[video->stream_id]->time_base,
+                              (AVRational) { 1, G_USEC_PER_SEC });
+  av_frame_free (&frame);
+
+  return texture;
+}
+
+static void
+gtk_demo_video_open_ffmpeg (GtkDemoVideo *video)
+{
+  AVStream *stream;
+  AVCodec *codec;
+  int errnum;
+
+  errnum = avformat_open_input (&video->format_ctx, video->filename, NULL, NULL);
+  if (errnum != 0)
+    {
+      gtk_demo_video_set_ffmpeg_error (video, errnum);
+      return;
+    }
+  
+  errnum = avformat_find_stream_info (video->format_ctx, NULL);
+  if (errnum < 0)
+    {
+      gtk_demo_video_set_ffmpeg_error (video, errnum);
+      return;
+    }
+  
+  video->stream_id = av_find_best_stream (video->format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
+  if (video->stream_id < 0)
+    {
+      gtk_demo_video_set_error (video, "File contains no video");
+      return;
+    }
+  
+  stream = video->format_ctx->streams[video->stream_id];
+  codec = avcodec_find_decoder (stream->codecpar->codec_id);
+  if (codec == NULL)
+    {
+      gtk_demo_video_set_error (video, "Unsupported video codec");
+      return;
+    }
+
+  video->codec_ctx = avcodec_alloc_context3 (codec);
+  errnum = avcodec_parameters_to_context (video->codec_ctx, stream->codecpar);
+  if (errnum < 0)
+    {
+      gtk_demo_video_set_ffmpeg_error (video, errnum);
+      return;
+    }
+  errnum = avcodec_open2(video->codec_ctx, codec, NULL);
+  if (errnum < 0)
+    {
+      gtk_demo_video_set_ffmpeg_error (video, errnum);
+      return;
+    }
+
+  video->sws_ctx = sws_getContext(video->codec_ctx->width,
+                                  video->codec_ctx->height,
+                                  video->codec_ctx->pix_fmt,
+                                  video->codec_ctx->width,
+                                  video->codec_ctx->height,
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                  AV_PIX_FMT_BGRA,
+#else
+                                  AV_PIX_FMT_ARGB,
+#endif
+                                  SWS_BILINEAR,
+                                  NULL,
+                                  NULL,
+                                  NULL);
+
+  gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
+
+  video->current_texture = gtk_demo_video_decode_frame (video, &video->current_texture_duration);
+
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
+}
+
+#endif /* HAVE_FFMPEG */
+
+static void
+gtk_demo_video_open (GtkDemoVideo *video)
+{
+  if (video->filename == NULL)
+    return;
+
+#ifdef HAVE_FFMPEG
+  gtk_demo_video_open_ffmpeg (video);
+#else
+  gtk_demo_video_set_error (video, "Video support not enabled at build time.");
+#endif
+}
+
+void
+gtk_demo_video_set_filename (GtkDemoVideo *video,
+                             const char   *filename)
+{
+  gtk_demo_video_clear (video);
+
+  video->filename = g_strdup (filename);
+
+  gtk_demo_video_open (video);
+
+  g_object_notify_by_pspec (G_OBJECT (video), properties[PROP_FILENAME]);
+}
+
+static gboolean
+gtk_demo_video_next_frame_cb (gpointer data);
+static void
+gtk_demo_video_queue_frame (GtkDemoVideo *video)
+{
+  gint64 time, frame_time;
+  guint delay;
+  
+  time = g_get_monotonic_time ();
+  frame_time = video->timestamp + video->current_texture_duration;
+  delay = time > frame_time ? 0 : (frame_time - time) / 1000;
+
+  video->next_frame_cb = g_timeout_add (delay, gtk_demo_video_next_frame_cb, video);
+}
+
+static gboolean
+gtk_demo_video_next_frame_cb (gpointer data)
+{
+  GtkDemoVideo *video = data;
+  GdkTexture *next_texture;
+  gint64 next_duration;
+  
+  video->next_frame_cb = 0;
+#ifdef HAVE_FFMPEG
+  next_texture = gtk_demo_video_decode_frame (video, &next_duration);
+#else
+  next_texture = NULL;
+  next_duration = 0;
+#endif
+  if (next_texture)
+    {
+      video->timestamp += video->current_texture_duration;
+      g_clear_object (&video->current_texture);
+      gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
+      video->current_texture_duration = next_duration;
+      video->current_texture = next_texture;
+
+      gtk_demo_video_queue_frame (video);
+    }
+  else
+    {
+      video->current_texture_duration = 0;
+      gtk_demo_video_stop (video);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_demo_video_play (GtkDemoVideo *video)
+{
+  if (video->playing)
+    return;
+
+  if (video->current_texture_duration == 0)
+    return;
+
+  video->timestamp = g_get_monotonic_time ();
+  video->playing = TRUE;
+  g_object_notify_by_pspec (G_OBJECT (video), properties[PROP_PLAYING]);
+
+  gtk_demo_video_queue_frame (video);
+}
+
+void
+gtk_demo_video_set_playing (GtkDemoVideo *video,
+                            gboolean      playing)
+{
+  if (video->playing == playing)
+    return;
+
+  if (playing)
+    gtk_demo_video_play (video);
+  else
+    gtk_demo_video_stop (video);
+
+  video->playing = playing;
+  g_object_notify_by_pspec (G_OBJECT (video), properties[PROP_PLAYING]);
+}
diff --git a/demos/gtk-demo/gtkdemovideo.h b/demos/gtk-demo/gtkdemovideo.h
new file mode 100644
index 0000000000..62e11ebb61
--- /dev/null
+++ b/demos/gtk-demo/gtkdemovideo.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_DEMO_VIDEO_H__
+#define __GTK_DEMO_VIDEO_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_DEMO_VIDEO (gtk_demo_video_get_type ())
+
+G_DECLARE_FINAL_TYPE (GtkDemoVideo, gtk_demo_video, GTK, DEMO_VIDEO, GObject)
+
+GtkDemoVideo *  gtk_demo_video_new                      (void);
+GtkDemoVideo *  gtk_demo_video_new_for_filename         (const char             *filename);
+
+void            gtk_demo_video_set_filename             (GtkDemoVideo           *video,
+                                                         const char             *filename);
+void            gtk_demo_video_set_playing              (GtkDemoVideo           *video,
+                                                         gboolean                playing);
+
+G_END_DECLS
+
+#endif /* __GTK_DEMO_VIDEO_H__ */
diff --git a/demos/gtk-demo/images.c b/demos/gtk-demo/images.c
index c3f0f675f6..b76db7e7cd 100644
--- a/demos/gtk-demo/images.c
+++ b/demos/gtk-demo/images.c
@@ -17,6 +17,8 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "gtkdemovideo.h"
+
 static GtkWidget *window = NULL;
 static GdkPixbufLoader *pixbuf_loader = NULL;
 static guint load_timeout = 0;
@@ -317,12 +319,15 @@ toggle_sensitivity_callback (GtkWidget *togglebutton,
 GtkWidget *
 do_images (GtkWidget *do_widget)
 {
+  GtkDemoVideo *video;
   GtkWidget *frame;
   GtkWidget *vbox;
+  GtkWidget *hbox;
+  GtkWidget *base_vbox;
   GtkWidget *image;
   GtkWidget *label;
   GtkWidget *button;
-  GIcon     *gicon;
+  GIcon *gicon;
 
   if (!window)
     {
@@ -336,9 +341,15 @@ do_images (GtkWidget *do_widget)
       g_signal_connect (window, "destroy",
                         G_CALLBACK (cleanup_callback), NULL);
 
+      base_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+      g_object_set (base_vbox, "margin", 16, NULL);
+      gtk_container_add (GTK_CONTAINER (window), base_vbox);
+
+      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
+      gtk_container_add (GTK_CONTAINER (base_vbox), hbox);
+
       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
-      g_object_set (vbox, "margin", 16, NULL);
-      gtk_container_add (GTK_CONTAINER (window), vbox);
+      gtk_container_add (GTK_CONTAINER (hbox), vbox);
 
       label = gtk_label_new (NULL);
       gtk_label_set_markup (GTK_LABEL (label),
@@ -395,6 +406,8 @@ do_images (GtkWidget *do_widget)
 
 
       /* Progressive */
+      vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+      gtk_container_add (GTK_CONTAINER (hbox), vbox);
 
       label = gtk_label_new (NULL);
       gtk_label_set_markup (GTK_LABEL (label),
@@ -415,13 +428,33 @@ do_images (GtkWidget *do_widget)
 
       start_progressive_loading (image);
 
+      /* Video */
+      vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+      gtk_container_add (GTK_CONTAINER (hbox), vbox);
+
+      label = gtk_label_new (NULL);
+      gtk_label_set_markup (GTK_LABEL (label),
+                            "<u>Displaying video</u>");
+      gtk_box_pack_start (GTK_BOX (vbox), label);
+
+      frame = gtk_frame_new (NULL);
+      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+      gtk_widget_set_halign (frame, GTK_ALIGN_CENTER);
+      gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
+      gtk_box_pack_start (GTK_BOX (vbox), frame);
+
+      video = gtk_demo_video_new_for_filename ("/home/lvs/incredibles2.mp4");
+      image = gtk_image_new_from_paintable (GDK_PAINTABLE (video));
+      gtk_container_add (GTK_CONTAINER (frame), image);
+      g_object_unref (video);
+
       /* Sensitivity control */
       button = gtk_toggle_button_new_with_mnemonic ("_Insensitive");
-      gtk_box_pack_start (GTK_BOX (vbox), button);
+      gtk_box_pack_start (GTK_BOX (base_vbox), button);
 
       g_signal_connect (button, "toggled",
                         G_CALLBACK (toggle_sensitivity_callback),
-                        vbox);
+                        base_vbox);
     }
 
   if (!gtk_widget_get_visible (window))
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 0364c849e3..fc5a034cdb 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -77,6 +77,10 @@ if harfbuzz_dep.found() and pangoft_dep.found()
   gtkdemo_deps += [ harfbuzz_dep, ]
 endif
 
+if ffmpeg_enabled
+  gtkdemo_deps += ffmpeg_deps
+endif
+
 if os_unix
   demos += files('pagesetup.c')
 endif
@@ -94,7 +98,7 @@ gtkdemo_resources = gnome.compile_resources('gtkdemo_resources',
 
 executable('gtk4-demo',
            'main.c', 'gtkfishbowl.c', 'fontplane.c', 'script-names.c', 'language-names.c',
-                   'gtkgears.c',
+                   'gtkgears.c', 'gtkdemovideo.c',
                    demos, demos_h, gtkdemo_resources,
            c_args: gtkdemo_args,
            dependencies: gtkdemo_deps,
diff --git a/demos/gtk-demo/widgetbowl.c b/demos/gtk-demo/widgetbowl.c
index aadee03610..d1a31c7896 100644
--- a/demos/gtk-demo/widgetbowl.c
+++ b/demos/gtk-demo/widgetbowl.c
@@ -8,6 +8,7 @@
 #include <gtk/gtk.h>
 
 #include "gtkfishbowl.h"
+#include "gtkdemovideo.h"
 #include "gtkgears.h"
 
 const char *const css =
@@ -81,6 +82,15 @@ create_label (void)
   return w;
 }
 
+static GtkWidget *
+create_video (void)
+{
+  GtkDemoVideo *video = gtk_demo_video_new_for_filename ("/home/lvs/incredibles2.mp4");
+  GtkWidget *w = gtk_image_new_from_paintable (GDK_PAINTABLE (video));
+
+  return w;
+}
+
 static GtkWidget *
 create_gears (void)
 {
@@ -102,6 +112,7 @@ static const struct {
   { "Label"     , create_label          },
   { "Spinner"   , create_spinner        },
   { "Spinbutton", create_spinbutton     },
+  { "Videos",     create_video          },
   { "Gears",      create_gears          },
 };
 
diff --git a/meson.build b/meson.build
index b75a085370..9d0c8c5d59 100644
--- a/meson.build
+++ b/meson.build
@@ -596,6 +596,17 @@ if cloudproviders_enabled
   endif
 endif
 
+ffmpeg_enabled = get_option('ffmpeg')
+if ffmpeg_enabled
+  libavfilter_dep = dependency('libavfilter', version: '>= 6.47.100', required: true)
+  libavformat_dep = dependency('libavformat', version: '>= 57.41.100', required: true)
+  libavcodec_dep = dependency('libavcodec', version: '>= 57.48.101', required: true)
+  libavutil_dep = dependency('libavutil', version: '>= 55.28.100', required: true)
+  libswscale_dep = dependency('libswscale', version: '>= 4.6.100', required: true)
+  ffmpeg_deps = [libavfilter_dep, libavformat_dep, libavcodec_dep, libavutil_dep, libswscale_dep]
+  cdata.set('HAVE_FFMPEG', 1)
+endif
+
 subdir('modules/input')
 subdir('gdk')
 subdir('gsk')
diff --git a/meson_options.txt b/meson_options.txt
index 896347e10b..6e68c8f786 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -17,6 +17,8 @@ option('xinerama', type: 'combo', choices : ['yes', 'no', 'auto'], value : 'auto
   description : 'Enable support for the Xinerama extension')
 option('cloudproviders', type: 'boolean', value: false,
   description : 'Enable the cloudproviders support')
+option('ffmpeg', type: 'boolean', value: false,
+  description : 'Enable ffmpeg video demos')
 
 # Print backends
 option('print-backends', type : 'string', value : 'cups,papi',


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