summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2018-03-01 04:52:27 +0100
committerBenjamin Otte <otte@redhat.com>2018-03-18 21:01:23 +0100
commit7cf6da60da07f3b88c7a2b7900e47df8f743991e (patch)
tree8921af8ac9a761c7fa4932d484dbb33ddd68a025
parent9700a98f4867ee523e9366419c36d580c57c88fa (diff)
downloadgtk+-7cf6da60da07f3b88c7a2b7900e47df8f743991e.tar.gz
gtk: Add GtkVideo
GtkVideo is a simple video player widget. It probably needs some more configurability, but it does its job.
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtkvideo.c615
-rw-r--r--gtk/gtkvideo.h64
-rw-r--r--gtk/meson.build2
-rw-r--r--gtk/theme/Adwaita/_common.scss13
-rw-r--r--gtk/ui/gtkvideo.ui47
6 files changed, 742 insertions, 0 deletions
diff --git a/gtk/gtk.h b/gtk/gtk.h
index afe849c292..b6f73e6108 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -228,6 +228,7 @@
#include <gtk/gtktypebuiltins.h>
#include <gtk/gtktypes.h>
#include <gtk/gtkversion.h>
+#include <gtk/gtkvideo.h>
#include <gtk/gtkviewport.h>
#include <gtk/gtkvolumebutton.h>
#include <gtk/gtkwidget.h>
diff --git a/gtk/gtkvideo.c b/gtk/gtkvideo.c
new file mode 100644
index 0000000000..bf51a236f8
--- /dev/null
+++ b/gtk/gtkvideo.c
@@ -0,0 +1,615 @@
+/*
+ * 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 "gtkvideo.h"
+
+#include "gtkeventcontrollermotion.h"
+#include "gtkimage.h"
+#include "gtkintl.h"
+#include "gtkmediacontrols.h"
+#include "gtkmediafile.h"
+#include "gtkrevealer.h"
+
+/**
+ * SECTION:gtkvideo
+ * @title: GtkVideo
+ * @short_description: A widget for displaying video
+ *
+ * GtkVideo is a widget to show a #GtkMediaStream.
+ */
+
+struct _GtkVideo
+{
+ GtkWidget parent_instance;
+
+ GFile *file;
+ GtkMediaStream *media_stream;
+
+ GtkEventController *motion_controller;
+
+ GtkWidget *box;
+ GtkWidget *video_image;
+ GtkWidget *overlay_icon;
+ GtkWidget *controls_revealer;
+ GtkWidget *controls;
+ guint controls_hide_source;
+};
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_MEDIA_STREAM,
+
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+gtk_video_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ GtkVideo *self = GTK_VIDEO (widget);
+
+ gtk_widget_measure (self->box,
+ orientation,
+ for_size,
+ minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void
+gtk_video_size_allocate (GtkWidget *widget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip)
+{
+ GtkVideo *self = GTK_VIDEO (widget);
+
+ gtk_widget_size_allocate (self->box, allocation, baseline, out_clip);
+}
+
+static void
+gtk_video_unmap (GtkWidget *widget)
+{
+ GtkVideo *self = GTK_VIDEO (widget);
+
+ if (self->controls_hide_source)
+ {
+ g_source_remove (self->controls_hide_source);
+ self->controls_hide_source = 0;
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
+ }
+
+ /* XXX: pause video here? */
+
+ GTK_WIDGET_CLASS (gtk_video_parent_class)->unmap (widget);
+}
+
+static void
+gtk_video_dispose (GObject *object)
+{
+ GtkVideo *self = GTK_VIDEO (object);
+
+ gtk_video_set_media_stream (self, NULL);
+
+ g_clear_object (&self->motion_controller);
+ g_clear_pointer (&self->box, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (gtk_video_parent_class)->dispose (object);
+}
+
+static void
+gtk_video_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkVideo *self = GTK_VIDEO (object);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_MEDIA_STREAM:
+ g_value_set_object (value, self->media_stream);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_video_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkVideo *self = GTK_VIDEO (object);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ gtk_video_set_file (self, g_value_get_object (value));
+ break;
+
+ case PROP_MEDIA_STREAM:
+ gtk_video_set_media_stream (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_video_class_init (GtkVideoClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ widget_class->measure = gtk_video_measure;
+ widget_class->size_allocate = gtk_video_size_allocate;
+ widget_class->unmap = gtk_video_unmap;
+
+ gobject_class->dispose = gtk_video_dispose;
+ gobject_class->get_property = gtk_video_get_property;
+ gobject_class->set_property = gtk_video_set_property;
+
+ /**
+ * GtkVideo:file:
+ *
+ * The file played by this video if the video is playing a file.
+ */
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ P_("File"),
+ P_("The video file played back"),
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkVideo:media-stream:
+ *
+ * The media-stream played
+ */
+ properties[PROP_MEDIA_STREAM] =
+ g_param_spec_object ("media-stream",
+ P_("Media Stream"),
+ P_("The media stream played"),
+ GTK_TYPE_MEDIA_STREAM,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkvideo.ui");
+ gtk_widget_class_bind_template_child (widget_class, GtkVideo, box);
+ gtk_widget_class_bind_template_child (widget_class, GtkVideo, video_image);
+ gtk_widget_class_bind_template_child (widget_class, GtkVideo, overlay_icon);
+ gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls);
+ gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls_revealer);
+
+ gtk_widget_class_set_css_name (widget_class, I_("video"));
+}
+
+static gboolean
+gtk_video_hide_controls (gpointer data)
+{
+ GtkVideo *self = data;
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
+
+ self->controls_hide_source = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_video_motion (GtkEventControllerMotion *motion,
+ double x,
+ double y,
+ GtkVideo *self)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), TRUE);
+ if (self->controls_hide_source)
+ g_source_remove (self->controls_hide_source);
+ self->controls_hide_source = g_timeout_add (5 * 1000,
+ gtk_video_hide_controls,
+ self);
+}
+
+static void
+gtk_video_init (GtkVideo *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+ self->motion_controller = gtk_event_controller_motion_new (GTK_WIDGET (self));
+ g_signal_connect (self->motion_controller, "motion", G_CALLBACK (gtk_video_motion), self);
+}
+
+/**
+ * gtk_video_new:
+ *
+ * Creates a new empty #GtkVideo.
+ *
+ * Returns: a new #GtkVideo
+ **/
+GtkWidget *
+gtk_video_new (void)
+{
+ return g_object_new (GTK_TYPE_VIDEO, NULL);
+}
+
+/**
+ * gtk_video_new_for_media_stream:
+ * @stream: (allow-none): a #GtkMediaStream
+ *
+ * Creates a #GtkVideo to play back the given @stream.
+ *
+ * Returns: a new #GtkVideo
+ **/
+GtkWidget *
+gtk_video_new_for_media_stream (GtkMediaStream *stream)
+{
+ g_return_val_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream), NULL);
+
+ return g_object_new (GTK_TYPE_VIDEO,
+ "media-stream", stream,
+ NULL);
+}
+
+/**
+ * gtk_video_new_for_file:
+ * @file: (allow-none): a #GFile
+ *
+ * Creates a #GtkVideo to play back the given @file.
+ *
+ * Returns: a new #GtkVideo
+ **/
+GtkWidget *
+gtk_video_new_for_file (GFile *file)
+{
+ g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+
+ return g_object_new (GTK_TYPE_VIDEO,
+ "file", file,
+ NULL);
+}
+
+/**
+ * gtk_video_new_for_filename:
+ * @filename: (allow-none): filename to play back
+ *
+ * Creates a #GtkVideo to play back the given @filename.
+ *
+ * This is a utility function that calls gtk_video_new_for_file(),
+ *
+ * Returns: a new #GtkVideo
+ **/
+GtkWidget *
+gtk_video_new_for_filename (const char *filename)
+{
+ GtkWidget *result;
+ GFile *file;
+
+ if (filename)
+ file = g_file_new_for_path (filename);
+ else
+ file = NULL;
+
+ result = gtk_video_new_for_file (file);
+
+ if (file)
+ g_object_unref (file);
+
+ return result;
+}
+
+/**
+ * gtk_video_new_for_resource:
+ * @filename: (allow-none): resource path to play back
+ *
+ * Creates a #GtkVideo to play back the resource at the
+ * given @resource_path.
+ *
+ * This is a utility function that calls gtk_video_new_for_file(),
+ *
+ * Returns: a new #GtkVideo
+ **/
+GtkWidget *
+gtk_video_new_for_resource (const char *resource_path)
+{
+ GtkWidget *result;
+ GFile *file;
+
+ if (resource_path)
+ {
+ char *uri, *escaped;
+
+ escaped = g_uri_escape_string (resource_path,
+ G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ uri = g_strconcat ("resource://", escaped, NULL);
+ g_free (escaped);
+
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+ else
+ {
+ file = NULL;
+ }
+
+ result = gtk_video_new_for_file (file);
+
+ if (file)
+ g_object_unref (file);
+
+ return result;
+}
+
+/**
+ * gtk_video_get_media_stream:
+ * @self: a #GtkVideo
+ *
+ * Gets the media stream managed by @self or %NULL if none.
+ *
+ * Returns: (nullable): The media stream managed by @self
+ **/
+GtkMediaStream *
+gtk_video_get_media_stream (GtkVideo *self)
+{
+ g_return_val_if_fail (GTK_IS_VIDEO (self), NULL);
+
+ return self->media_stream;
+}
+
+static void
+gtk_video_update_overlay_icon (GtkVideo *self)
+{
+ const char *icon_name;
+ const GError *error = NULL;
+
+ if (self->media_stream == NULL)
+ icon_name = "media-eject-symbolic";
+ else if ((error = gtk_media_stream_get_error (self->media_stream)))
+ icon_name = "dialog-error-symbolic";
+ else if (gtk_media_stream_get_ended (self->media_stream))
+ icon_name = "media-playlist-repeat-symbolic";
+ else
+ icon_name = "media-playback-start-symbolic";
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (self->overlay_icon), icon_name);
+ if (error)
+ gtk_widget_set_tooltip_text (self->overlay_icon, error->message);
+ else
+ gtk_widget_set_tooltip_text (self->overlay_icon, NULL);
+}
+
+static void
+gtk_video_update_ended (GtkVideo *self)
+{
+ gtk_video_update_overlay_icon (self);
+}
+
+static void
+gtk_video_update_error (GtkVideo *self)
+{
+ gtk_video_update_overlay_icon (self);
+}
+
+static void
+gtk_video_update_playing (GtkVideo *self)
+{
+ gboolean playing;
+
+ if (self->media_stream != NULL)
+ playing = gtk_media_stream_get_playing (self->media_stream);
+ else
+ playing = FALSE;
+
+ gtk_widget_set_visible (self->overlay_icon, !playing);
+}
+
+static void
+gtk_video_update_all (GtkVideo *self)
+{
+ gtk_video_update_ended (self);
+ gtk_video_update_error (self);
+ gtk_video_update_playing (self);
+}
+
+static void
+gtk_video_notify_cb (GtkMediaStream *stream,
+ GParamSpec *pspec,
+ GtkVideo *self)
+{
+ if (g_str_equal (pspec->name, "ended"))
+ gtk_video_update_ended (self);
+ if (g_str_equal (pspec->name, "error"))
+ gtk_video_update_error (self);
+ if (g_str_equal (pspec->name, "playing"))
+ gtk_video_update_playing (self);
+}
+
+/**
+ * gtk_video_set_media_stream:
+ * @self: a #GtkVideo
+ * @stream: (allow-none): The media stream to play or %NULL to unset
+ *
+ * Sets the media stream to be played back. @self will take full control
+ * of managing the media stream. If you want to manage a media stream
+ * yourself, consider using a #GtkImage for display.
+ *
+ * If you want to display a file, consider using gtk_video_set_file()
+ * instead.
+ **/
+void
+gtk_video_set_media_stream (GtkVideo *self,
+ GtkMediaStream *stream)
+{
+ g_return_if_fail (GTK_IS_VIDEO (self));
+ g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
+
+ if (self->media_stream == stream)
+ return;
+
+ if (self->media_stream)
+ {
+ g_signal_handlers_disconnect_by_func (self->media_stream,
+ gtk_video_notify_cb,
+ self);
+ g_object_unref (self->media_stream);
+ self->media_stream = NULL;
+ }
+
+ if (stream)
+ {
+ self->media_stream = g_object_ref (stream);
+ g_signal_connect (self->media_stream,
+ "notify",
+ G_CALLBACK (gtk_video_notify_cb),
+ self);
+ }
+
+ gtk_media_controls_set_media_stream (GTK_MEDIA_CONTROLS (self->controls), stream);
+ gtk_image_set_from_paintable (GTK_IMAGE (self->video_image), GDK_PAINTABLE (stream));
+
+ gtk_video_update_all (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MEDIA_STREAM]);
+}
+
+/**
+ * gtk_video_set_file:
+ * @self: a #GtkVideo
+ * @file: (allow-none): the file to play
+ *
+ * Makes @self play the given @file.
+ **/
+void
+gtk_video_set_file (GtkVideo *self,
+ GFile *file)
+{
+ GtkMediaStream *stream;
+
+ g_return_if_fail (GTK_IS_VIDEO (self));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ if (!g_set_object (&self->file, file))
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ if (file)
+ stream = gtk_media_file_new_for_file (file);
+ else
+ stream = NULL;
+
+ gtk_video_set_media_stream (self, stream);
+
+ if (stream)
+ g_object_unref (stream);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_video_set_resource:
+ * @self: a #GtkVideo
+ * @filename: (allow-none): the filename to play
+ *
+ * Makes @self play the given @filename.
+ *
+ * This is a utility function that calls gtk_video_set_file(),
+ **/
+void
+gtk_video_set_filename (GtkVideo *self,
+ const char *filename)
+{
+ GFile *file;
+
+ g_return_if_fail (GTK_IS_VIDEO (self));
+
+ if (filename)
+ file = g_file_new_for_path (filename);
+ else
+ file = NULL;
+
+ gtk_video_set_file (self, file);
+
+ if (file)
+ g_object_unref (file);
+}
+
+/**
+ * gtk_video_set_resource:
+ * @self: a #GtkVideo
+ * @resource_path: (allow-none): the resource to set
+ *
+ * Makes @self play the resource at the given @resource_path.
+ *
+ * This is a utility function that calls gtk_video_set_file(),
+ **/
+void
+gtk_video_set_resource (GtkVideo *self,
+ const char *resource_path)
+{
+ GFile *file;
+
+ g_return_if_fail (GTK_IS_VIDEO (self));
+
+ if (resource_path)
+ {
+ char *uri, *escaped;
+
+ escaped = g_uri_escape_string (resource_path,
+ G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ uri = g_strconcat ("resource://", escaped, NULL);
+ g_free (escaped);
+
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+ else
+ {
+ file = NULL;
+ }
+
+ gtk_video_set_file (self, file);
+
+ if (file)
+ g_object_unref (file);
+}
+
diff --git a/gtk/gtkvideo.h b/gtk/gtkvideo.h
new file mode 100644
index 0000000000..16ffbbfcc6
--- /dev/null
+++ b/gtk/gtkvideo.h
@@ -0,0 +1,64 @@
+/*
+ * 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_VIDEO_H__
+#define __GTK_VIDEO_H__
+
+#include <gtk/gtkmediastream.h>
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_VIDEO (gtk_video_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkVideo, gtk_video, GTK, VIDEO, GtkWidget)
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_video_new (void);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_video_new_for_media_stream (GtkMediaStream *stream);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_video_new_for_file (GFile *file);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_video_new_for_filename (const char *filename);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_video_new_for_resource (const char *resource_path);
+
+GDK_AVAILABLE_IN_ALL
+GtkMediaStream *gtk_video_get_media_stream (GtkVideo *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_video_set_media_stream (GtkVideo *self,
+ GtkMediaStream *stream);
+GDK_AVAILABLE_IN_ALL
+GFile * gtk_video_get_file (GtkVideo *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_video_set_file (GtkVideo *self,
+ GFile *file);
+GDK_AVAILABLE_IN_ALL
+void gtk_video_set_filename (GtkVideo *self,
+ const char *filename);
+GDK_AVAILABLE_IN_ALL
+void gtk_video_set_resource (GtkVideo *self,
+ const char *resource_path);
+
+
+G_END_DECLS
+
+#endif /* __GTK_VIDEO_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 22d849f2e9..c06b5aa075 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -358,6 +358,7 @@ gtk_public_sources = files([
'gtktreeview.c',
'gtktreeviewcolumn.c',
'gtkutils.c',
+ 'gtkvideo.c',
'gtkviewport.c',
'gtkvolumebutton.c',
'gtkwidget.c',
@@ -573,6 +574,7 @@ gtk_public_headers = files([
'gtktreeview.h',
'gtktreeviewcolumn.h',
'gtktypes.h',
+ 'gtkvideo.h',
'gtkviewport.h',
'gtkvolumebutton.h',
'gtkwidget.h',
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index 3e28edd0f3..783df4fbf6 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -3999,6 +3999,19 @@ paned {
/**************
+ * GtkVideo *
+ **************/
+
+video {
+ & image.osd {
+ min-width: 64px;
+ min-height: 64px;
+ border-radius: 32px;
+ }
+ background: black;
+}
+
+/**************
* GtkInfoBar *
**************/
infobar {
diff --git a/gtk/ui/gtkvideo.ui b/gtk/ui/gtkvideo.ui
new file mode 100644
index 0000000000..8acd989bc2
--- /dev/null
+++ b/gtk/ui/gtkvideo.ui
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <!-- interface-requires gtk+ 3.6 -->
+ <template class="GtkVideo" parent="GtkWidget">
+ <child>
+ <object class="GtkOverlay" id="box">
+ <child>
+ <object class="GtkImage" id="video_image">
+ <property name="can-shrink">1</property>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkImage" id="overlay_icon">
+ <style>
+ <class name="osd"/>
+ <class name="circular"/>
+ </style>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="icon-name">media-playback-start-symbolic</property>
+ <property name="icon-size">large</property>
+ </object>
+ <packing>
+ <property name="measure">1</property>
+ </packing>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="controls_revealer">
+ <property name="reveal-child">0</property>
+ <property name="valign">end</property>
+ <child>
+ <object class="GtkMediaControls" id="controls">
+ <style>
+ <class name="osd"/>
+ <class name="bottom"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="measure">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>