/* * 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 . * * Authors: Benjamin Otte */ #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_default_realize (GtkMediaStream *self, GdkSurface *surface) { } static void gtk_media_stream_default_unrealize (GtkMediaStream *self, GdkSurface *surface) { } 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; class->realize = gtk_media_stream_default_realize; class->unrealize = gtk_media_stream_default_unrealize; 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_realize: * @self: a #GtkMediaStream * @surface: a #GdkSurface * * Called by users to attach the media stream to a #GdkSurface they manage. * The stream can then access the resources of @surface for its rendering * purposes. In particular, media streams might want to create * #GdkGLContexts or sync to the #GdkFrameClock. * * Whoever calls this function is responsible for calling * gtk_media_stream_unrealize() before either the stream or @surface get * destroyed. * * Multiple calls to this function may happen from different users of the * video, even with the same @surface. Each of these calls must be followed * by its own call to gtk_media_stream_unrealize(). * * It is not required to call this function to make a media stream work. **/ void gtk_media_stream_realize (GtkMediaStream *self, GdkSurface *surface) { g_return_if_fail (GTK_IS_MEDIA_STREAM (self)); g_return_if_fail (GDK_IS_SURFACE (surface)); g_object_ref (self); g_object_ref (surface); GTK_MEDIA_STREAM_GET_CLASS (self)->realize (self, surface); } /** * gtk_media_stream_unrealize: * @self: a #GtkMediaStream previously realized * @surface: the #GdkSurface the stream was realized with * * Undoes a previous call to gtk_media_stream_realize() and causes * the stream to release all resources it had allocated from @surface. **/ void gtk_media_stream_unrealize (GtkMediaStream *self, GdkSurface *surface) { g_return_if_fail (GTK_IS_MEDIA_STREAM (self)); g_return_if_fail (GDK_IS_SURFACE (surface)); GTK_MEDIA_STREAM_GET_CLASS (self)->unrealize (self, surface); g_object_unref (surface); g_object_unref (self); } /** * 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_valist: * @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]); }