[gtk] gtk: Add GtkMediaStream



commit 4db1a5f5c4216cef20afe872ea99264c63b72eb3
Author: Benjamin Otte <otte redhat com>
Date:   Tue Feb 27 23:22:26 2018 +0100

    gtk: Add GtkMediaStream
    
    GtkMediaStream is the new base class for playback of audio and video
    streams.
    
    It implements GdkPaintable for rendering.

 gtk/gtk.h            |    1 +
 gtk/gtkmediastream.c | 1192 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkmediastream.h |  144 ++++++
 gtk/meson.build      |    2 +
 4 files changed, 1339 insertions(+)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 5c43fd6205..dcfcb55dc0 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -137,6 +137,7 @@
 #include <gtk/gtkliststore.h>
 #include <gtk/gtklockbutton.h>
 #include <gtk/gtkmain.h>
+#include <gtk/gtkmediastream.h>
 #include <gtk/gtkmenu.h>
 #include <gtk/gtkmenubar.h>
 #include <gtk/gtkmenubutton.h>
diff --git a/gtk/gtkmediastream.c b/gtk/gtkmediastream.c
new file mode 100644
index 0000000000..aadec85622
--- /dev/null
+++ b/gtk/gtkmediastream.c
@@ -0,0 +1,1192 @@
+/*
+ * 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 "gtkmediastream.h"
+
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkmediastream
+ * @Short_description: Display media in GTK
+ * @Title: GtkMediaStream
+ * @See_also: #GdkPaintable
+ *
+ * #GtkMediaStream is the integration point for media playback inside GTK.
+ *
+ * FIXME: Write more about how frameworks should implement this thing and how
+ * GTK widgets exist (once they do) that consume it.
+ */
+
+typedef struct _GtkMediaStreamPrivate GtkMediaStreamPrivate;
+
+struct _GtkMediaStreamPrivate
+{
+  gint64 timestamp;
+  gint64 duration;
+  GError *error;
+  double volume;
+
+  guint has_audio : 1;
+  guint has_video : 1;
+  guint playing : 1;
+  guint ended : 1;
+  guint seekable : 1;
+  guint seeking : 1;
+  guint loop : 1;
+  guint prepared : 1;
+  guint muted : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_PREPARED,
+  PROP_ERROR,
+  PROP_HAS_AUDIO,
+  PROP_HAS_VIDEO,
+  PROP_PLAYING,
+  PROP_ENDED,
+  PROP_TIMESTAMP,
+  PROP_DURATION,
+  PROP_SEEKABLE,
+  PROP_SEEKING,
+  PROP_LOOP,
+  PROP_MUTED,
+  PROP_VOLUME,
+
+  N_PROPS,
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+gtk_media_stream_paintable_snapshot (GdkPaintable *paintable,
+                                     GdkSnapshot  *snapshot,
+                                     double        width,
+                                     double        height)
+{
+}
+
+static void
+gtk_media_stream_paintable_init (GdkPaintableInterface *iface)
+{
+  /* We implement the behavior for "no video stream" here */
+  iface->snapshot = gtk_media_stream_paintable_snapshot;
+}
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMediaStream, gtk_media_stream, G_TYPE_OBJECT,
+                                  G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                                         gtk_media_stream_paintable_init)
+                                  G_ADD_PRIVATE (GtkMediaStream))
+
+#define GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
+  g_critical ("Media stream of type '%s' does not implement GtkMediaStream::" # method, G_OBJECT_TYPE_NAME 
(obj))
+
+static gboolean
+gtk_media_stream_default_play (GtkMediaStream *self)
+{
+  GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD (self, play);
+
+  return FALSE;
+}
+
+static void
+gtk_media_stream_default_pause (GtkMediaStream *self)
+{
+  GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD (self, pause);
+}
+
+static void
+gtk_media_stream_default_seek (GtkMediaStream *self,
+                               gint64          timestamp)
+{
+  gtk_media_stream_seek_failed (self);
+}
+
+static void
+gtk_media_stream_default_update_audio (GtkMediaStream *self,
+                                       gboolean        muted,
+                                       double          volume)
+{
+}
+
+static void
+gtk_media_stream_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+
+{
+  GtkMediaStream *self = GTK_MEDIA_STREAM (object);
+
+  switch (prop_id)
+    {
+    case PROP_PLAYING:
+      gtk_media_stream_set_playing (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_LOOP:
+      gtk_media_stream_set_loop (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_MUTED:
+      gtk_media_stream_set_muted (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_VOLUME:
+      gtk_media_stream_set_volume (self, g_value_get_double (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_media_stream_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GtkMediaStream *self = GTK_MEDIA_STREAM (object);
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_PREPARED:
+      g_value_set_boolean (value, priv->prepared);
+      break;
+
+    case PROP_ERROR:
+      g_value_set_boxed (value, priv->error);
+      break;
+
+    case PROP_HAS_AUDIO:
+      g_value_set_boolean (value, priv->has_audio);
+      break;
+
+    case PROP_HAS_VIDEO:
+      g_value_set_boolean (value, priv->has_video);
+      break;
+
+    case PROP_PLAYING:
+      g_value_set_boolean (value, priv->playing);
+      break;
+
+    case PROP_ENDED:
+      g_value_set_boolean (value, priv->ended);
+      break;
+
+    case PROP_TIMESTAMP:
+      g_value_set_int64 (value, priv->timestamp);
+      break;
+
+    case PROP_DURATION:
+      g_value_set_int64 (value, priv->duration);
+      break;
+
+    case PROP_SEEKABLE:
+      g_value_set_boolean (value, priv->seekable);
+      break;
+
+    case PROP_SEEKING:
+      g_value_set_boolean (value, priv->seeking);
+      break;
+
+    case PROP_LOOP:
+      g_value_set_boolean (value, priv->loop);
+      break;
+
+    case PROP_MUTED:
+      g_value_set_boolean (value, priv->muted);
+      break;
+
+    case PROP_VOLUME:
+      g_value_set_double (value, priv->volume);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_media_stream_dispose (GObject *object)
+{
+  GtkMediaStream *self = GTK_MEDIA_STREAM (object);
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_clear_error (&priv->error);
+
+  G_OBJECT_CLASS (gtk_media_stream_parent_class)->dispose (object);
+}
+
+static void
+gtk_media_stream_finalize (GObject *object)
+{
+#if 0
+  GtkMediaStream *self = GTK_MEDIA_STREAM (object);
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+#endif
+
+  G_OBJECT_CLASS (gtk_media_stream_parent_class)->finalize (object);
+}
+
+static void
+gtk_media_stream_class_init (GtkMediaStreamClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  class->play = gtk_media_stream_default_play;
+  class->pause = gtk_media_stream_default_pause;
+  class->seek = gtk_media_stream_default_seek;
+  class->update_audio = gtk_media_stream_default_update_audio;
+
+  gobject_class->set_property = gtk_media_stream_set_property;
+  gobject_class->get_property = gtk_media_stream_get_property;
+  gobject_class->finalize = gtk_media_stream_finalize;
+  gobject_class->dispose = gtk_media_stream_dispose;
+
+  /**
+   * GtkMediaStream:prepared:
+   *
+   * Whether the stream has finished initializing and existence of
+   * audio and video is known.
+   */
+  properties[PROP_PREPARED] =
+    g_param_spec_boolean ("prepared",
+                          P_("Prepared"),
+                          P_("Whether the stream has finished initializing"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:error:
+   *
+   * %NULL for a properly working stream or the #GError that the stream is in.
+   */
+  properties[PROP_ERROR] =
+    g_param_spec_boxed ("error",
+                        P_("Error"),
+                        P_("Error the stream is in"),
+                        G_TYPE_ERROR,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:has-audio:
+   *
+   * Whether the stream contains audio
+   */
+  properties[PROP_HAS_AUDIO] =
+    g_param_spec_boolean ("has-audio",
+                          P_("Has audio"),
+                          P_("Whether the stream contains audio"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:has-video:
+   *
+   * Whether the stream contains video
+   */
+  properties[PROP_HAS_VIDEO] =
+    g_param_spec_boolean ("has-video",
+                          P_("Has video"),
+                          P_("Whether the stream contains video"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:playing:
+   *
+   * Whether the stream is currently playing.
+   */
+  properties[PROP_PLAYING] =
+    g_param_spec_boolean ("playing",
+                          P_("Playing"),
+                          P_("Whether the stream is playing"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:ended:
+   *
+   * Set when playback has finished.
+   */
+  properties[PROP_ENDED] =
+    g_param_spec_boolean ("ended",
+                          P_("Ended"),
+                          P_("Set when playback has finished"),
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:timestamp:
+   *
+   * The current presentation timestamp in microseconds.
+   */
+  properties[PROP_TIMESTAMP] =
+    g_param_spec_int64 ("timestamp",
+                        P_("Timestamp"),
+                        P_("Timestamp in microseconds"),
+                        0, G_MAXINT64, 0,
+                        G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:duration:
+   *
+   * The stream's duration in microseconds or 0 if unknown.
+   */
+  properties[PROP_DURATION] =
+    g_param_spec_int64 ("duration",
+                        P_("Duration"),
+                        P_("Timestamp in microseconds"),
+                        0, G_MAXINT64, 0,
+                        G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:seekable:
+   *
+   * Set unless the stream is known to not support seeking.
+   */
+  properties[PROP_SEEKABLE] =
+    g_param_spec_boolean ("seekable",
+                          P_("Seekable"),
+                          P_("Set unless seeking is not supported"),
+                          TRUE,
+                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:seeking:
+   *
+   * Set while a seek is in progress.
+   */
+  properties[PROP_SEEKING] =
+    g_param_spec_boolean ("seeking",
+                          P_("Seeking"),
+                          P_("Set while a seek is in progress"),
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:loop:
+   *
+   * Try to restart the media from the beginning once it ended.
+   */
+  properties[PROP_LOOP] =
+    g_param_spec_boolean ("loop",
+                          P_("Loop"),
+                          P_("Try to restart the media from the beginning once it ended."),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:muted:
+   *
+   * Whether the audio stream should be muted.
+   */
+  properties[PROP_MUTED] =
+    g_param_spec_boolean ("muted",
+                          P_("Muted"),
+                          P_("Whether the audio stream should be muted."),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkMediaStream:volume:
+   *
+   * Volume of the audio stream.
+   */
+  properties[PROP_VOLUME] =
+    g_param_spec_boolean ("volume",
+                          P_("Volume"),
+                          P_("Volume of the audio stream."),
+                          1.0,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+static void
+gtk_media_stream_init (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  priv->volume = 1.0;
+}
+
+gboolean
+gtk_media_stream_is_prepared (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->prepared;
+}
+
+gboolean
+gtk_media_stream_has_audio (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->has_audio;
+}
+
+gboolean
+gtk_media_stream_has_video (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->has_video;
+}
+
+void
+gtk_media_stream_play (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  if (priv->error)
+    return;
+
+  if (priv->playing)
+    return;
+
+  if (GTK_MEDIA_STREAM_GET_CLASS (self)->play (self))
+    {
+      g_object_freeze_notify (G_OBJECT (self));
+
+      priv->playing = TRUE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLAYING]);
+
+      if (priv->ended)
+        {
+          priv->ended = FALSE;
+          g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
+        }
+
+      g_object_thaw_notify (G_OBJECT (self));
+    }
+}
+
+void
+gtk_media_stream_pause (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  /* Don't check for error here because we call this function right
+   * after setting the error to pause the stream */
+
+  if (!priv->playing)
+    return;
+
+  GTK_MEDIA_STREAM_GET_CLASS (self)->pause (self);
+
+  priv->playing = FALSE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLAYING]);
+}
+
+gboolean
+gtk_media_stream_get_playing (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->playing;
+}
+
+void
+gtk_media_stream_set_playing (GtkMediaStream *self,
+                              gboolean        playing)
+{
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  if (playing)
+    gtk_media_stream_play (self);
+  else
+    gtk_media_stream_pause (self);
+}
+
+gboolean
+gtk_media_stream_get_ended (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->ended;
+}
+
+gint64
+gtk_media_stream_get_timestamp (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->timestamp;
+}
+
+/**
+ * gtk_media_stream_get_duration:
+ * @self: a #GtkmediaStream
+ *
+ * Gets the duration of the stream. If the duration is not known,
+ * 0 will be returned.
+ *
+ * Returns: the duration of the stream or 0 if not known.
+ **/
+gint64
+gtk_media_stream_get_duration (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->duration;
+}
+
+/**
+ * gtk_media_stream_is_seekable:
+ * @self: a #GtkMediaStream
+ *
+ * Checks if a stream may be seekable.
+ *
+ * This is meant to be a hint. Streams may not allow seeking even if
+ * this function returns %TRUE. However, if this function returns
+ * %FALSE, streams are guaranteed to not be seekable and user interfaces
+ * may hide controls that allow seeking.
+ *
+ * It is allowed to call gtk_media_stream_seek() on a non-seekable
+ * stream, though it will not do anything.
+ *
+ * Returns: %TRUE if the stream may support seeking
+ **/
+gboolean
+gtk_media_stream_is_seekable (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->seekable;
+}
+
+/**
+ * gtk_media_stream_is_seeking:
+ * @self: a #GtkMediaStream
+ *
+ * Checks if there is currently a seek operation going on.
+ *
+ * Returns: %TRUE if a seek operation is ongoing.
+ **/
+gboolean
+gtk_media_stream_is_seeking (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->seeking;
+}
+
+/**
+ * gtk_media_stream_get_error:
+ * @self: a #GtkMediaStream
+ *
+ * If the stream is in an error state, returns the #GError explaining that state.
+ * Any type of error can be reported here depending on the implementation of the
+ * media stream.
+ *
+ * A media stream in an error cannot be operated on, calls like
+ * gtk_media_stream_play() or gtk_media_stream_seek() will not have any effect.
+ *
+ * #GtkMediaStream itself does not provide a way to unset an error, but
+ * implementations may provide options. For example, a #GtkMediaFile will unset
+ * errors when a new source is set with ie gtk_media_file_set_file().
+ *
+ * Returns: (nullable) (transfer none): %NULL if not in an error state or
+ *    the #GError of the stream
+ **/
+const GError *
+gtk_media_stream_get_error (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->error;
+}
+
+/**
+ * gtk_media_stream_seek:
+ * @self: a #GtkMediaStream
+ * @timestamp: timestamp to seek to.
+ *
+ * Start a seek operation on @self to @timestamp. If @timestamp is out of range,
+ * it will be clamped.
+ *
+ * Seek operations may not finish instantly. While a seek operation is
+ * in process, the GtkMediaStream:seeking property will be set.
+ *
+ * When calling gtk_media_stream_seek() during an ongoing seek operation,
+ * the new seek wil override any pending seek.
+ **/
+void
+gtk_media_stream_seek (GtkMediaStream *self,
+                       gint64          timestamp)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+  gboolean was_seeking;
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (timestamp >= 0);
+
+  if (priv->error)
+    return;
+
+  if (!priv->seekable)
+    return;
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  was_seeking = priv->seeking;
+  priv->seeking = TRUE;
+
+  GTK_MEDIA_STREAM_GET_CLASS (self)->seek (self, timestamp);
+
+  if (was_seeking != priv->seeking)
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_get_loop:
+ * @self: a #GtkMediaStream
+ *
+ * Returns whether the stream is set to loop. See
+ * gtk_media_stream_set_loop() for details.
+ *
+ * Returns: %TRUE if the stream should loop
+ **/
+gboolean
+gtk_media_stream_get_loop (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->loop;
+}
+
+/**
+ * gtk_media_stream_set_loop:
+ * @self: a #GtkMediaStream
+ * @loop: %TRUE if the stream should loop
+ *
+ * Sets whether the stream should loop, ie restart playback from
+ * the beginning instead of stopping at the end.
+ *
+ * Not all streams may support looping, in particular non-seekable
+ * streams. Those streams will ignore the loop setting and just end.
+ **/
+void
+gtk_media_stream_set_loop (GtkMediaStream *self,
+                           gboolean        loop)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  if (priv->loop == loop)
+    return;
+
+  priv->loop = loop;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOOP]);
+}
+
+/**
+ * gtk_media_stream_get_muted:
+ * @self: a #GtkMediaStream
+ *
+ * Returns whether the audio for the stream is muted.
+ * See gtk_media_stream_set_muted() for details.
+ *
+ * Returns: %TRUE if the stream is muted
+ **/
+gboolean
+gtk_media_stream_get_muted (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->muted;
+}
+
+/**
+ * gtk_media_stream_set_muted:
+ * @self: a #GtkMediaStream
+ * @muted: %TRUE if the stream should be muted
+ *
+ * Sets whether the audio stream should be muted. Muting a stream will
+ * cause no audio to be played, but it does not modify the volume.
+ * This means that muting and then unmuting the stream will restore
+ * the volume settings.
+ *
+ * If the stream has no audio, calling this function will still work
+ * but it will not have an audible effect.
+ **/
+void
+gtk_media_stream_set_muted (GtkMediaStream *self,
+                            gboolean        muted)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  if (priv->muted == muted)
+    return;
+
+  priv->muted = muted;
+
+  GTK_MEDIA_STREAM_GET_CLASS (self)->update_audio (self, priv->muted, priv->volume);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MUTED]);
+}
+
+/**
+ * gtk_media_stream_get_volume:
+ * @self: a #GtkMediaStream
+ *
+ * Returns the volume of the audio for the stream.
+ * See gtk_media_stream_set_volume() for details.
+ *
+ * Returns: volume of the stream from 0.0 to 1.0
+ **/
+double
+gtk_media_stream_get_volume (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
+
+  return priv->volume;
+}
+
+/**
+ * gtk_media_stream_set_volume:
+ * @self: a #GtkMediaStream
+ * @volume: New volume of the stream from 0.0 to 1.0
+ *
+ * Sets the volume of the audio stream. This function call will work even if
+ * the stream is muted.
+ *
+ * The given @volume should range from 0.0 for silence to 1.0 for as loud as
+ * possible. Values outside of this range will be clamped to the nearest
+ * value.
+ *
+ * If the stream has no audio or is muted, calling this function will still
+ * work but it will not have an immediate audible effect. When the stream is
+ * unmuted, the new volume setting will take effect.
+ **/
+void
+gtk_media_stream_set_volume (GtkMediaStream *self,
+                             double          volume)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  volume = CLAMP (volume, 0.0, 1.0);
+
+  if (priv->volume == volume)
+    return;
+
+  priv->volume = volume;
+
+  GTK_MEDIA_STREAM_GET_CLASS (self)->update_audio (self, priv->muted, priv->volume);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VOLUME]);
+}
+
+/**
+ * gtk_media_stream_prepared:
+ * @self: a #GtkMediaStream
+ * @has_audio: %TRUE if the stream should advertise audio support
+ * @has_video: %TRUE if the stream should advertise video support
+ * @seekable: %TRUE if the stream should advertise seekability
+ * @duration: The duration of the stream or 0 if unknown
+ *
+ * Called by #GtkMediaStream implementations to advertise the stream
+ * being ready to play and providing details about the stream.
+ *
+ * Note that the arguments are hints. If the stream implementation
+ * cannot determine the correct values, it is better to err on the
+ * side of caution and return %TRUE. User interfaces will use those
+ * values to determine what controls to show.
+ *
+ * This function may not be called again until the stream has been
+ * reset via gtk_media_stream_unprepared().
+ **/
+void
+gtk_media_stream_prepared (GtkMediaStream *self,
+                           gboolean        has_audio,
+                           gboolean        has_video,
+                           gboolean        seekable,
+                           gint64          duration)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (!gtk_media_stream_is_prepared (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  if (priv->has_audio != has_audio)
+    {
+      priv->has_audio = has_audio;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_AUDIO]);
+    }
+  if (priv->has_video != has_video)
+    {
+      priv->has_video = has_video;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_VIDEO]);
+    }
+  if (priv->seekable != seekable)
+    {
+      priv->seekable = seekable;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKABLE]);
+    }
+  if (priv->duration != duration)
+    {
+      priv->duration = duration;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
+    }
+
+  priv->prepared = TRUE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_unprepared:
+ * @self: a #GtkMediaStream
+ *
+ * Resets a given media stream implementation. gtk_media_stream_prepared()
+ * can now be called again.
+ *
+ * This function will also reset any error state the stream was in.
+ **/
+void
+gtk_media_stream_unprepared (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (gtk_media_stream_is_prepared (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  gtk_media_stream_pause (self);
+
+  if (priv->has_audio)
+    {
+      priv->has_audio = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_AUDIO]);
+    }
+  if (priv->has_video)
+    {
+      priv->has_video = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_VIDEO]);
+    }
+  if (priv->seekable)
+    {
+      priv->seekable = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKABLE]);
+    }
+  if (priv->seeking)
+    {
+      priv->seeking = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
+    }
+  if (priv->duration != 0)
+    {
+      priv->duration = 0;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
+    }
+  if (priv->timestamp != 0)
+    {
+      priv->timestamp = 0;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]);
+    }
+  if (priv->error)
+    {
+      g_clear_error (&priv->error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+    }
+
+  priv->prepared = FALSE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_gerror:
+ * @self: a #GtkMediaStream
+ * @error: (transfer full): the #GError to set
+ *
+ * Sets @self into an error state. This will pause the stream
+ * (you can check for an error via gtk_media_stream_get_error() in
+ * your GtkMediaStream.pause() implementation), abort pending seeks
+ * and mark the stream as prepared.
+ *
+ * To unset an error, the stream must be reset via a call to
+ * gtk_media_stream_unprepared().
+ **/
+void
+gtk_media_stream_gerror (GtkMediaStream *self,
+                         GError         *error)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (gtk_media_stream_get_error (self) == NULL);
+  g_return_if_fail (error != NULL);
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  priv->error = error;
+
+  gtk_media_stream_pause (self);
+
+  if (!priv->prepared)
+    {
+      priv->prepared = TRUE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
+    }
+  
+  if (priv->seeking)
+    gtk_media_stream_seek_failed (self);
+  
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_error:
+ * @self: a #GtkMediaStream
+ * @domain: error domain
+ * @code: error code
+ * @format: printf()-style format for error message
+ * @...: parameters for message format
+ *
+ * Sets @self into an error state using a printf()-style format string.
+ *
+ * This is a utility function that calls gtk_media_stream_gerror(). See
+ * that function for details.  
+ **/
+void
+gtk_media_stream_error (GtkMediaStream *self,
+                        GQuark          domain,
+                        gint            code,
+                        const gchar    *format,
+                        ...)
+{
+  GError *error;
+  va_list args;
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (domain != 0);
+  g_return_if_fail (format != NULL);
+
+  va_start (args, format);
+  error = g_error_new_valist (domain, code, format, args);
+  va_end (args);
+
+  gtk_media_stream_gerror (self, error);
+}
+
+/**
+ * gtk_media_stream_error:
+ * @self: a #GtkMediaStream
+ * @domain: error domain
+ * @code: error code
+ * @format: printf()-style format for error message
+ * @args: #va_list of parameters for the message format
+ *
+ * Sets @self into an error state using a printf()-style format string.
+ *
+ * This is a utility function that calls gtk_media_stream_gerror(). See
+ * that function for details.  
+ */
+void
+gtk_media_stream_error_valist (GtkMediaStream *self,
+                               GQuark          domain,
+                               gint            code,
+                               const gchar    *format,
+                               va_list         args)
+{
+  GError *error;
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (domain != 0);
+  g_return_if_fail (format != NULL);
+
+  error = g_error_new_valist (domain, code, format, args);
+
+  gtk_media_stream_gerror (self, error);
+}
+
+/**
+ * gtk_media_stream_update:
+ * @self: a #GtkMediaStream
+ * @timestamp: the new timestamp
+ *
+ * Media stream implementations should regularly call this function to
+ * update the timestamp reported by the stream. It is up to
+ * implementations to call this at the frequency they deem appropriate.
+ **/
+void
+gtk_media_stream_update (GtkMediaStream *self,
+                         gint64          timestamp)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  /* Timestamp before duration is important here.
+   * This way the duration notify will be emitted first which will
+   * make GtkMediaControls update adjustment->upper so that the
+   * timestamp notify will cause the timestamp to not be clamped.
+   */
+  if (priv->timestamp != timestamp)
+    {
+      priv->timestamp = timestamp;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]);
+    }
+  if (priv->duration > 0 && timestamp > priv->duration)
+    {
+      priv->duration = priv->timestamp;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
+    }
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_ended:
+ * @self: a #GtkMediaStream
+ *
+ * Pauses the media stream and marks it as ended. This is a hint only, calls
+ * to GtkMediaStream.play() may still happen.
+ **/
+void
+gtk_media_stream_ended (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (!gtk_media_stream_get_ended (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  gtk_media_stream_pause (self);
+
+  priv->ended = TRUE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_seek_success:
+ * @self: a #GtkMediaStream
+ *
+ * Ends a seek operation started via GtkMediaStream.seek() successfully.
+ * This function will unset the GtkMediaStream:ended property if it was
+ * set.
+ *
+ * See gtk_media_stream_seek_failed() for the other way of
+ * ending a seek.
+ **/
+void
+gtk_media_stream_seek_success (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (gtk_media_stream_is_seeking (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  priv->seeking = FALSE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
+
+  if (priv->ended)
+    {
+      priv->ended = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
+    }
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_media_stream_seek_failed:
+ * @self: a #GtkMediaStream
+ *
+ * Ends a seek operation started via GtkMediaStream.seek() as a failure.
+ * This will not cause an error on the stream and will assume that
+ * playback continues as if no seek had happened.
+ *
+ * See gtk_media_stream_seek_success() for the other way of
+ * ending a seek.
+ **/
+void
+gtk_media_stream_seek_failed (GtkMediaStream *self)
+{
+  GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
+  g_return_if_fail (gtk_media_stream_is_seeking (self));
+
+  priv->seeking = FALSE;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
+}
+
diff --git a/gtk/gtkmediastream.h b/gtk/gtkmediastream.h
new file mode 100644
index 0000000000..3029696518
--- /dev/null
+++ b/gtk/gtkmediastream.h
@@ -0,0 +1,144 @@
+/*
+ * 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_MEDIA_STREAM_H__
+#define __GTK_MEDIA_STREAM_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_MEDIA_STREAM             (gtk_media_stream_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (GtkMediaStream, gtk_media_stream, GTK, MEDIA_STREAM, GObject)
+
+struct _GtkMediaStreamClass
+{
+  GObjectClass parent_class;
+
+  gboolean              (* play)                                (GtkMediaStream *self);
+  void                  (* pause)                               (GtkMediaStream *self);
+  void                  (* seek)                                (GtkMediaStream *self,
+                                                                 gint64          timestamp);
+  void                  (* update_audio)                        (GtkMediaStream *self,
+                                                                 gboolean        muted,
+                                                                 double          volume);
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+  void (*_gtk_reserved5) (void);
+  void (*_gtk_reserved6) (void);
+  void (*_gtk_reserved7) (void);
+  void (*_gtk_reserved8) (void);
+};
+
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_is_prepared            (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+const GError *          gtk_media_stream_get_error              (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_has_audio              (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_has_video              (GtkMediaStream *self);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_play                   (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_pause                  (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_get_playing            (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_set_playing            (GtkMediaStream *self,
+                                                                 gboolean        playing);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_get_ended              (GtkMediaStream *self);
+
+GDK_AVAILABLE_IN_ALL
+gint64                  gtk_media_stream_get_timestamp          (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+gint64                  gtk_media_stream_get_duration           (GtkMediaStream *self);
+
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_is_seekable            (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_is_seeking             (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_seek                   (GtkMediaStream *self,
+                                                                 gint64          timestamp);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_get_loop               (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_set_loop               (GtkMediaStream *self,
+                                                                 gboolean        loop);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_media_stream_get_muted              (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_set_muted              (GtkMediaStream *self,
+                                                                 gboolean        muted);
+GDK_AVAILABLE_IN_ALL
+double                  gtk_media_stream_get_volume             (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_set_volume             (GtkMediaStream *self,
+                                                                 double          volume);
+
+/* for implementations only */
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_prepared               (GtkMediaStream *self,
+                                                                 gboolean        has_audio,
+                                                                 gboolean        has_video,
+                                                                 gboolean        seekable,
+                                                                 gint64          duration);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_unprepared             (GtkMediaStream *self);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_update                 (GtkMediaStream *self,
+                                                                 gint64          timestamp);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_ended                  (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_seek_success           (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_seek_failed            (GtkMediaStream *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_gerror                 (GtkMediaStream *self,
+                                                                 GError         *error);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_error                  (GtkMediaStream *self,
+                                                                 GQuark          domain,
+                                                                 gint            code,
+                                                                 const gchar    *format,
+                                                                 ...) G_GNUC_PRINTF (4, 5);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_media_stream_error_valist           (GtkMediaStream *self,
+                                                                 GQuark          domain,
+                                                                 gint            code,
+                                                                 const gchar    *format,
+                                                                 va_list         args) G_GNUC_PRINTF (4, 0);
+
+G_END_DECLS
+
+#endif /* __GTK_MEDIA_STREAM_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 619b709b1b..630c2e0bed 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -251,6 +251,7 @@ gtk_public_sources = files([
   'gtkliststore.c',
   'gtklockbutton.c',
   'gtkmain.c',
+  'gtkmediastream.c',
   'gtkmenu.c',
   'gtkmenubar.c',
   'gtkmenubutton.c',
@@ -480,6 +481,7 @@ gtk_public_headers = files([
   'gtkliststore.h',
   'gtklockbutton.h',
   'gtkmain.h',
+  'gtkmediastream.h',
   'gtkmenu.h',
   'gtkmenubar.h',
   'gtkmenubutton.h',



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