1 /*
2  * Copyright © 2018 Benjamin Otte
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19 
20 #include "config.h"
21 
22 #include "gtkmediastream.h"
23 
24 #include "gtkintl.h"
25 
26 /**
27  * GtkMediaStream:
28  *
29  * `GtkMediaStream` is the integration point for media playback inside GTK.
30  *
31  * GTK provides an implementation of the `GtkMediaStream` interface that
32  * is called [class@Gtk.MediaFile].
33  *
34  * Apart from application-facing API for stream playback, `GtkMediaStream`
35  * has a number of APIs that are only useful for implementations and should
36  * not be used in applications:
37  * [method@Gtk.MediaStream.prepared],
38  * [method@Gtk.MediaStream.unprepared],
39  * [method@Gtk.MediaStream.update],
40  * [method@Gtk.MediaStream.ended],
41  * [method@Gtk.MediaStream.seek_success],
42  * [method@Gtk.MediaStream.seek_failed],
43  * [method@Gtk.MediaStream.gerror],
44  * [method@Gtk.MediaStream.error],
45  * [method@Gtk.MediaStream.error_valist].
46  */
47 
48 typedef struct _GtkMediaStreamPrivate GtkMediaStreamPrivate;
49 
50 struct _GtkMediaStreamPrivate
51 {
52   gint64 timestamp;
53   gint64 duration;
54   GError *error;
55   double volume;
56 
57   guint has_audio : 1;
58   guint has_video : 1;
59   guint playing : 1;
60   guint ended : 1;
61   guint seekable : 1;
62   guint seeking : 1;
63   guint loop : 1;
64   guint prepared : 1;
65   guint muted : 1;
66 };
67 
68 enum {
69   PROP_0,
70   PROP_PREPARED,
71   PROP_ERROR,
72   PROP_HAS_AUDIO,
73   PROP_HAS_VIDEO,
74   PROP_PLAYING,
75   PROP_ENDED,
76   PROP_TIMESTAMP,
77   PROP_DURATION,
78   PROP_SEEKABLE,
79   PROP_SEEKING,
80   PROP_LOOP,
81   PROP_MUTED,
82   PROP_VOLUME,
83 
84   N_PROPS,
85 };
86 
87 static GParamSpec *properties[N_PROPS] = { NULL, };
88 
89 static void
gtk_media_stream_paintable_snapshot(GdkPaintable * paintable,GdkSnapshot * snapshot,double width,double height)90 gtk_media_stream_paintable_snapshot (GdkPaintable *paintable,
91                                      GdkSnapshot  *snapshot,
92                                      double        width,
93                                      double        height)
94 {
95 }
96 
97 static void
gtk_media_stream_paintable_init(GdkPaintableInterface * iface)98 gtk_media_stream_paintable_init (GdkPaintableInterface *iface)
99 {
100   /* We implement the behavior for "no video stream" here */
101   iface->snapshot = gtk_media_stream_paintable_snapshot;
102 }
103 
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))104 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMediaStream, gtk_media_stream, G_TYPE_OBJECT,
105                                   G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
106                                                          gtk_media_stream_paintable_init)
107                                   G_ADD_PRIVATE (GtkMediaStream))
108 
109 #define GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
110   g_critical ("Media stream of type '%s' does not implement GtkMediaStream::" # method, G_OBJECT_TYPE_NAME (obj))
111 
112 static gboolean
113 gtk_media_stream_default_play (GtkMediaStream *self)
114 {
115   GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD (self, play);
116 
117   return FALSE;
118 }
119 
120 static void
gtk_media_stream_default_pause(GtkMediaStream * self)121 gtk_media_stream_default_pause (GtkMediaStream *self)
122 {
123   GTK_MEDIA_STREAM_WARN_NOT_IMPLEMENTED_METHOD (self, pause);
124 }
125 
126 static void
gtk_media_stream_default_seek(GtkMediaStream * self,gint64 timestamp)127 gtk_media_stream_default_seek (GtkMediaStream *self,
128                                gint64          timestamp)
129 {
130   gtk_media_stream_seek_failed (self);
131 }
132 
133 static void
gtk_media_stream_default_update_audio(GtkMediaStream * self,gboolean muted,double volume)134 gtk_media_stream_default_update_audio (GtkMediaStream *self,
135                                        gboolean        muted,
136                                        double          volume)
137 {
138 }
139 
140 static void
gtk_media_stream_default_realize(GtkMediaStream * self,GdkSurface * surface)141 gtk_media_stream_default_realize (GtkMediaStream *self,
142                                   GdkSurface      *surface)
143 {
144 }
145 
146 static void
gtk_media_stream_default_unrealize(GtkMediaStream * self,GdkSurface * surface)147 gtk_media_stream_default_unrealize (GtkMediaStream *self,
148                                     GdkSurface      *surface)
149 {
150 }
151 
152 static void
gtk_media_stream_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)153 gtk_media_stream_set_property (GObject      *object,
154                                guint         prop_id,
155                                const GValue *value,
156                                GParamSpec   *pspec)
157 
158 {
159   GtkMediaStream *self = GTK_MEDIA_STREAM (object);
160 
161   switch (prop_id)
162     {
163     case PROP_PLAYING:
164       gtk_media_stream_set_playing (self, g_value_get_boolean (value));
165       break;
166 
167     case PROP_LOOP:
168       gtk_media_stream_set_loop (self, g_value_get_boolean (value));
169       break;
170 
171     case PROP_MUTED:
172       gtk_media_stream_set_muted (self, g_value_get_boolean (value));
173       break;
174 
175     case PROP_VOLUME:
176       gtk_media_stream_set_volume (self, g_value_get_double (value));
177       break;
178 
179     default:
180       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181       break;
182     }
183 }
184 
185 static void
gtk_media_stream_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)186 gtk_media_stream_get_property (GObject    *object,
187                                guint       prop_id,
188                                GValue     *value,
189                                GParamSpec *pspec)
190 {
191   GtkMediaStream *self = GTK_MEDIA_STREAM (object);
192   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
193 
194   switch (prop_id)
195     {
196     case PROP_PREPARED:
197       g_value_set_boolean (value, priv->prepared);
198       break;
199 
200     case PROP_ERROR:
201       g_value_set_boxed (value, priv->error);
202       break;
203 
204     case PROP_HAS_AUDIO:
205       g_value_set_boolean (value, priv->has_audio);
206       break;
207 
208     case PROP_HAS_VIDEO:
209       g_value_set_boolean (value, priv->has_video);
210       break;
211 
212     case PROP_PLAYING:
213       g_value_set_boolean (value, priv->playing);
214       break;
215 
216     case PROP_ENDED:
217       g_value_set_boolean (value, priv->ended);
218       break;
219 
220     case PROP_TIMESTAMP:
221       g_value_set_int64 (value, priv->timestamp);
222       break;
223 
224     case PROP_DURATION:
225       g_value_set_int64 (value, priv->duration);
226       break;
227 
228     case PROP_SEEKABLE:
229       g_value_set_boolean (value, priv->seekable);
230       break;
231 
232     case PROP_SEEKING:
233       g_value_set_boolean (value, priv->seeking);
234       break;
235 
236     case PROP_LOOP:
237       g_value_set_boolean (value, priv->loop);
238       break;
239 
240     case PROP_MUTED:
241       g_value_set_boolean (value, priv->muted);
242       break;
243 
244     case PROP_VOLUME:
245       g_value_set_double (value, priv->volume);
246       break;
247 
248     default:
249       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
250       break;
251     }
252 }
253 
254 static void
gtk_media_stream_dispose(GObject * object)255 gtk_media_stream_dispose (GObject *object)
256 {
257   GtkMediaStream *self = GTK_MEDIA_STREAM (object);
258   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
259 
260   g_clear_error (&priv->error);
261 
262   G_OBJECT_CLASS (gtk_media_stream_parent_class)->dispose (object);
263 }
264 
265 static void
gtk_media_stream_finalize(GObject * object)266 gtk_media_stream_finalize (GObject *object)
267 {
268 #if 0
269   GtkMediaStream *self = GTK_MEDIA_STREAM (object);
270   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
271 #endif
272 
273   G_OBJECT_CLASS (gtk_media_stream_parent_class)->finalize (object);
274 }
275 
276 static void
gtk_media_stream_class_init(GtkMediaStreamClass * class)277 gtk_media_stream_class_init (GtkMediaStreamClass *class)
278 {
279   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
280 
281   class->play = gtk_media_stream_default_play;
282   class->pause = gtk_media_stream_default_pause;
283   class->seek = gtk_media_stream_default_seek;
284   class->update_audio = gtk_media_stream_default_update_audio;
285   class->realize = gtk_media_stream_default_realize;
286   class->unrealize = gtk_media_stream_default_unrealize;
287 
288   gobject_class->set_property = gtk_media_stream_set_property;
289   gobject_class->get_property = gtk_media_stream_get_property;
290   gobject_class->finalize = gtk_media_stream_finalize;
291   gobject_class->dispose = gtk_media_stream_dispose;
292 
293   /**
294    * GtkMediaStream:prepared: (attributes org.gtk.Property.get=gtk_media_stream_is_prepared)
295    *
296    * Whether the stream has finished initializing and existence of
297    * audio and video is known.
298    */
299   properties[PROP_PREPARED] =
300     g_param_spec_boolean ("prepared",
301                           P_("Prepared"),
302                           P_("Whether the stream has finished initializing"),
303                           FALSE,
304                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
305 
306   /**
307    * GtkMediaStream:error: (attributes org.gtk.Property.get=gtk_media_stream_get_error)
308    *
309    * %NULL for a properly working stream or the `GError`
310    * that the stream is in.
311    */
312   properties[PROP_ERROR] =
313     g_param_spec_boxed ("error",
314                         P_("Error"),
315                         P_("Error the stream is in"),
316                         G_TYPE_ERROR,
317                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
318 
319   /**
320    * GtkMediaStream:has-audio: (attributes org.gtk.Property.get=gtk_media_stream_has_audio)
321    *
322    * Whether the stream contains audio.
323    */
324   properties[PROP_HAS_AUDIO] =
325     g_param_spec_boolean ("has-audio",
326                           P_("Has audio"),
327                           P_("Whether the stream contains audio"),
328                           FALSE,
329                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
330 
331   /**
332    * GtkMediaStream:has-video: (attributes org.gtk.Property.get=gtk_media_stream_has_video)
333    *
334    * Whether the stream contains video.
335    */
336   properties[PROP_HAS_VIDEO] =
337     g_param_spec_boolean ("has-video",
338                           P_("Has video"),
339                           P_("Whether the stream contains video"),
340                           FALSE,
341                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
342 
343   /**
344    * GtkMediaStream:playing: (attributes org.gtk.Property.get=gtk_media_stream_get_playing org.gtk.Property.set=gtk_media_stream_set_playing)
345    *
346    * Whether the stream is currently playing.
347    */
348   properties[PROP_PLAYING] =
349     g_param_spec_boolean ("playing",
350                           P_("Playing"),
351                           P_("Whether the stream is playing"),
352                           FALSE,
353                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
354 
355   /**
356    * GtkMediaStream:ended: (attributes org.gtk.Property.get=gtk_media_stream_get_ended)
357    *
358    * Set when playback has finished.
359    */
360   properties[PROP_ENDED] =
361     g_param_spec_boolean ("ended",
362                           P_("Ended"),
363                           P_("Set when playback has finished"),
364                           FALSE,
365                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
366 
367   /**
368    * GtkMediaStream:timestamp: (attributes org.gtk.Property.get=gtk_media_stream_get_timestamp)
369    *
370    * The current presentation timestamp in microseconds.
371    */
372   properties[PROP_TIMESTAMP] =
373     g_param_spec_int64 ("timestamp",
374                         P_("Timestamp"),
375                         P_("Timestamp in microseconds"),
376                         0, G_MAXINT64, 0,
377                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
378 
379   /**
380    * GtkMediaStream:duration: (attributes org.gtk.Property.get=gtk_media_stream_get_duration)
381    *
382    * The stream's duration in microseconds or 0 if unknown.
383    */
384   properties[PROP_DURATION] =
385     g_param_spec_int64 ("duration",
386                         P_("Duration"),
387                         P_("Timestamp in microseconds"),
388                         0, G_MAXINT64, 0,
389                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
390 
391   /**
392    * GtkMediaStream:seekable: (attributes org.gtk.Property.get=gtk_media_stream_is_seekable)
393    *
394    * Set unless the stream is known to not support seeking.
395    */
396   properties[PROP_SEEKABLE] =
397     g_param_spec_boolean ("seekable",
398                           P_("Seekable"),
399                           P_("Set unless seeking is not supported"),
400                           TRUE,
401                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
402 
403   /**
404    * GtkMediaStream:seeking: (attributes org.gtk.Property.get=gtk_media_stream_is_seeking)
405    *
406    * Set while a seek is in progress.
407    */
408   properties[PROP_SEEKING] =
409     g_param_spec_boolean ("seeking",
410                           P_("Seeking"),
411                           P_("Set while a seek is in progress"),
412                           FALSE,
413                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
414 
415   /**
416    * GtkMediaStream:loop: (attributes org.gtk.Property.get=gtk_media_stream_get_loop org.gtk.Property.set=gtk_media_stream_set_loop)
417    *
418    * Try to restart the media from the beginning once it ended.
419    */
420   properties[PROP_LOOP] =
421     g_param_spec_boolean ("loop",
422                           P_("Loop"),
423                           P_("Try to restart the media from the beginning once it ended."),
424                           FALSE,
425                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
426 
427   /**
428    * GtkMediaStream:muted: (attributes org.gtk.Property.get=gtk_media_stream_get_muted org.gtk.Property.set=gtk_media_stream_set_muted)
429    *
430    * Whether the audio stream should be muted.
431    */
432   properties[PROP_MUTED] =
433     g_param_spec_boolean ("muted",
434                           P_("Muted"),
435                           P_("Whether the audio stream should be muted."),
436                           FALSE,
437                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
438 
439   /**
440    * GtkMediaStream:volume: (attributes org.gtk.Property.get=gtk_media_stream_get_volume org.gtk.Property.set=gtk_media_stream_set_volume)
441    *
442    * Volume of the audio stream.
443    */
444   properties[PROP_VOLUME] =
445     g_param_spec_double ("volume",
446                          P_("Volume"),
447                          P_("Volume of the audio stream."),
448                          0.0, 1.0, 1.0,
449                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
450 
451   g_object_class_install_properties (gobject_class, N_PROPS, properties);
452 }
453 
454 static void
gtk_media_stream_init(GtkMediaStream * self)455 gtk_media_stream_init (GtkMediaStream *self)
456 {
457   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
458 
459   priv->volume = 1.0;
460 }
461 
462 /**
463  * gtk_media_stream_is_prepared: (attributes org.gtk.Method.get_property=prepared)
464  * @self: a `GtkMediaStream`
465  *
466  * Returns whether the stream has finished initializing.
467  *
468  * At this point the existence of audio and video is known.
469  *
470  * Returns: %TRUE if the stream is prepared
471  */
472 gboolean
gtk_media_stream_is_prepared(GtkMediaStream * self)473 gtk_media_stream_is_prepared (GtkMediaStream *self)
474 {
475   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
476 
477   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
478 
479   return priv->prepared;
480 }
481 
482 /**
483  * gtk_media_stream_has_audio: (attributes org.gtk.Method.get_property=has-audio)
484  * @self: a `GtkMediaStream`
485  *
486  * Returns whether the stream has audio.
487  *
488  * Returns: %TRUE if the stream has audio
489  */
490 gboolean
gtk_media_stream_has_audio(GtkMediaStream * self)491 gtk_media_stream_has_audio (GtkMediaStream *self)
492 {
493   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
494 
495   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
496 
497   return priv->has_audio;
498 }
499 
500 /**
501  * gtk_media_stream_has_video: (attributes org.gtk.Method.get_property=has-video)
502  * @self: a `GtkMediaStream`
503  *
504  * Returns whether the stream has video.
505  *
506  * Returns: %TRUE if the stream has video
507  */
508 gboolean
gtk_media_stream_has_video(GtkMediaStream * self)509 gtk_media_stream_has_video (GtkMediaStream *self)
510 {
511   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
512 
513   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
514 
515   return priv->has_video;
516 }
517 
518 /**
519  * gtk_media_stream_play:
520  * @self: a `GtkMediaStream`
521  *
522  * Starts playing the stream.
523  *
524  * If the stream is in error or already playing, do nothing.
525  */
526 void
gtk_media_stream_play(GtkMediaStream * self)527 gtk_media_stream_play (GtkMediaStream *self)
528 {
529   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
530 
531   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
532 
533   if (priv->error)
534     return;
535 
536   if (priv->playing)
537     return;
538 
539   if (GTK_MEDIA_STREAM_GET_CLASS (self)->play (self))
540     {
541       g_object_freeze_notify (G_OBJECT (self));
542 
543       priv->playing = TRUE;
544       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLAYING]);
545 
546       if (priv->ended)
547         {
548           priv->ended = FALSE;
549           g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
550         }
551 
552       g_object_thaw_notify (G_OBJECT (self));
553     }
554 }
555 
556 /**
557  * gtk_media_stream_pause:
558  * @self: a `GtkMediaStream`
559  *
560  * Pauses playback of the stream.
561  *
562  * If the stream is not playing, do nothing.
563  */
564 void
gtk_media_stream_pause(GtkMediaStream * self)565 gtk_media_stream_pause (GtkMediaStream *self)
566 {
567   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
568 
569   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
570 
571   /* Don't check for error here because we call this function right
572    * after setting the error to pause the stream */
573 
574   if (!priv->playing)
575     return;
576 
577   GTK_MEDIA_STREAM_GET_CLASS (self)->pause (self);
578 
579   priv->playing = FALSE;
580   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PLAYING]);
581 }
582 
583 /**
584  * gtk_media_stream_get_playing: (attributes org.gtk.Method.get_property=playing)
585  * @self: a `GtkMediaStream`
586  *
587  * Return whether the stream is currently playing.
588  *
589  * Returns: %TRUE if the stream is playing
590  */
591 gboolean
gtk_media_stream_get_playing(GtkMediaStream * self)592 gtk_media_stream_get_playing (GtkMediaStream *self)
593 {
594   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
595 
596   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
597 
598   return priv->playing;
599 }
600 
601 /**
602  * gtk_media_stream_set_playing: (attributes org.gtk.Method.set_property=playing)
603  * @self: a `GtkMediaStream`
604  * @playing: whether to start or pause playback
605  *
606  * Starts or pauses playback of the stream.
607  */
608 void
gtk_media_stream_set_playing(GtkMediaStream * self,gboolean playing)609 gtk_media_stream_set_playing (GtkMediaStream *self,
610                               gboolean        playing)
611 {
612   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
613 
614   if (playing)
615     gtk_media_stream_play (self);
616   else
617     gtk_media_stream_pause (self);
618 }
619 
620 /**
621  * gtk_media_stream_get_ended: (attributes org.gtk.Method.get_property=ended)
622  * @self: a `GtkMediaStream`
623  *
624  * Returns whether the streams playback is finished.
625  *
626  * Returns: %TRUE if playback is finished
627  */
628 gboolean
gtk_media_stream_get_ended(GtkMediaStream * self)629 gtk_media_stream_get_ended (GtkMediaStream *self)
630 {
631   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
632 
633   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
634 
635   return priv->ended;
636 }
637 
638 /**
639  * gtk_media_stream_get_timestamp: (attributes org.gtk.Method.get_property=timestamp)
640  * @self: a `GtkMediaStream`
641  *
642  * Returns the current presentation timestamp in microseconds.
643  *
644  * Returns: the timestamp in microseconds
645  */
646 gint64
gtk_media_stream_get_timestamp(GtkMediaStream * self)647 gtk_media_stream_get_timestamp (GtkMediaStream *self)
648 {
649   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
650 
651   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
652 
653   return priv->timestamp;
654 }
655 
656 /**
657  * gtk_media_stream_get_duration: (attributes org.gtk.Method.get_property=duration)
658  * @self: a `GtkMediaStream`
659  *
660  * Gets the duration of the stream.
661  *
662  * If the duration is not known, 0 will be returned.
663  *
664  * Returns: the duration of the stream or 0 if not known.
665  */
666 gint64
gtk_media_stream_get_duration(GtkMediaStream * self)667 gtk_media_stream_get_duration (GtkMediaStream *self)
668 {
669   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
670 
671   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
672 
673   return priv->duration;
674 }
675 
676 /**
677  * gtk_media_stream_is_seekable: (attributes org.gtk.Method.get_property=seekable)
678  * @self: a `GtkMediaStream`
679  *
680  * Checks if a stream may be seekable.
681  *
682  * This is meant to be a hint. Streams may not allow seeking even if
683  * this function returns %TRUE. However, if this function returns
684  * %FALSE, streams are guaranteed to not be seekable and user interfaces
685  * may hide controls that allow seeking.
686  *
687  * It is allowed to call [method@Gtk.MediaStream.seek] on a non-seekable
688  * stream, though it will not do anything.
689  *
690  * Returns: %TRUE if the stream may support seeking
691  */
692 gboolean
gtk_media_stream_is_seekable(GtkMediaStream * self)693 gtk_media_stream_is_seekable (GtkMediaStream *self)
694 {
695   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
696 
697   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
698 
699   return priv->seekable;
700 }
701 
702 /**
703  * gtk_media_stream_is_seeking: (attributes org.gtk.Method.get_property=seeking)
704  * @self: a `GtkMediaStream`
705  *
706  * Checks if there is currently a seek operation going on.
707  *
708  * Returns: %TRUE if a seek operation is ongoing.
709  */
710 gboolean
gtk_media_stream_is_seeking(GtkMediaStream * self)711 gtk_media_stream_is_seeking (GtkMediaStream *self)
712 {
713   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
714 
715   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
716 
717   return priv->seeking;
718 }
719 
720 /**
721  * gtk_media_stream_get_error: (attributes org.gtk.Method.get_property=error)
722  * @self: a `GtkMediaStream`
723  *
724  * If the stream is in an error state, returns the `GError`
725  * explaining that state.
726  *
727  * Any type of error can be reported here depending on the
728  * implementation of the media stream.
729  *
730  * A media stream in an error cannot be operated on, calls
731  * like [method@Gtk.MediaStream.play] or
732  * [method@Gtk.MediaStream.seek] will not have any effect.
733  *
734  * `GtkMediaStream` itself does not provide a way to unset
735  * an error, but implementations may provide options. For example,
736  * a [class@Gtk.MediaFile] will unset errors when a new source is
737  * set, e.g. with [method@Gtk.MediaFile.set_file].
738  *
739  * Returns: (nullable) (transfer none): %NULL if not in an
740  *   error state or the `GError` of the stream
741  */
742 const GError *
gtk_media_stream_get_error(GtkMediaStream * self)743 gtk_media_stream_get_error (GtkMediaStream *self)
744 {
745   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
746 
747   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
748 
749   return priv->error;
750 }
751 
752 /**
753  * gtk_media_stream_seek:
754  * @self: a `GtkMediaStream`
755  * @timestamp: timestamp to seek to.
756  *
757  * Start a seek operation on @self to @timestamp.
758  *
759  * If @timestamp is out of range, it will be clamped.
760  *
761  * Seek operations may not finish instantly. While a
762  * seek operation is in process, the [property@Gtk.MediaStream:seeking]
763  * property will be set.
764  *
765  * When calling gtk_media_stream_seek() during an
766  * ongoing seek operation, the new seek will override
767  * any pending seek.
768  */
769 void
gtk_media_stream_seek(GtkMediaStream * self,gint64 timestamp)770 gtk_media_stream_seek (GtkMediaStream *self,
771                        gint64          timestamp)
772 {
773   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
774   gboolean was_seeking;
775 
776   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
777   g_return_if_fail (timestamp >= 0);
778 
779   if (priv->error)
780     return;
781 
782   if (!priv->seekable)
783     return;
784 
785   g_object_freeze_notify (G_OBJECT (self));
786 
787   was_seeking = priv->seeking;
788   priv->seeking = TRUE;
789 
790   GTK_MEDIA_STREAM_GET_CLASS (self)->seek (self, timestamp);
791 
792   if (was_seeking != priv->seeking)
793     g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
794 
795   g_object_thaw_notify (G_OBJECT (self));
796 }
797 
798 /**
799  * gtk_media_stream_get_loop: (attributes org.gtk.Method.get_property=loop)
800  * @self: a `GtkMediaStream`
801  *
802  * Returns whether the stream is set to loop.
803  *
804  * See [method@Gtk.MediaStream.set_loop] for details.
805  *
806  * Returns: %TRUE if the stream should loop
807  */
808 gboolean
gtk_media_stream_get_loop(GtkMediaStream * self)809 gtk_media_stream_get_loop (GtkMediaStream *self)
810 {
811   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
812 
813   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
814 
815   return priv->loop;
816 }
817 
818 /**
819  * gtk_media_stream_set_loop: (attributes org.gtk.Method.set_property=loop)
820  * @self: a `GtkMediaStream`
821  * @loop: %TRUE if the stream should loop
822  *
823  * Sets whether the stream should loop.
824  *
825  * In this case, it will attempt to restart playback
826  * from the beginning instead of stopping at the end.
827  *
828  * Not all streams may support looping, in particular
829  * non-seekable streams. Those streams will ignore the
830  * loop setting and just end.
831  */
832 void
gtk_media_stream_set_loop(GtkMediaStream * self,gboolean loop)833 gtk_media_stream_set_loop (GtkMediaStream *self,
834                            gboolean        loop)
835 {
836   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
837 
838   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
839 
840   if (priv->loop == loop)
841     return;
842 
843   priv->loop = loop;
844   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOOP]);
845 }
846 
847 /**
848  * gtk_media_stream_get_muted: (attributes org.gtk.Method.get_property=muted)
849  * @self: a `GtkMediaStream`
850  *
851  * Returns whether the audio for the stream is muted.
852  *
853  * See [method@Gtk.MediaStream.set_muted] for details.
854  *
855  * Returns: %TRUE if the stream is muted
856  */
857 gboolean
gtk_media_stream_get_muted(GtkMediaStream * self)858 gtk_media_stream_get_muted (GtkMediaStream *self)
859 {
860   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
861 
862   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
863 
864   return priv->muted;
865 }
866 
867 /**
868  * gtk_media_stream_set_muted: (attributes org.gtk.Method.set_property=muted)
869  * @self: a `GtkMediaStream`
870  * @muted: %TRUE if the stream should be muted
871  *
872  * Sets whether the audio stream should be muted.
873  *
874  * Muting a stream will cause no audio to be played, but it
875  * does not modify the volume. This means that muting and
876  * then unmuting the stream will restore the volume settings.
877  *
878  * If the stream has no audio, calling this function will
879  * still work but it will not have an audible effect.
880  */
881 void
gtk_media_stream_set_muted(GtkMediaStream * self,gboolean muted)882 gtk_media_stream_set_muted (GtkMediaStream *self,
883                             gboolean        muted)
884 {
885   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
886 
887   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
888 
889   if (priv->muted == muted)
890     return;
891 
892   priv->muted = muted;
893 
894   GTK_MEDIA_STREAM_GET_CLASS (self)->update_audio (self, priv->muted, priv->volume);
895 
896   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MUTED]);
897 }
898 
899 /**
900  * gtk_media_stream_get_volume: (attributes org.gtk.Method.get_property=volume)
901  * @self: a `GtkMediaStream`
902  *
903  * Returns the volume of the audio for the stream.
904  *
905  * See [method@Gtk.MediaStream.set_volume] for details.
906  *
907  * Returns: volume of the stream from 0.0 to 1.0
908  */
909 double
gtk_media_stream_get_volume(GtkMediaStream * self)910 gtk_media_stream_get_volume (GtkMediaStream *self)
911 {
912   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
913 
914   g_return_val_if_fail (GTK_IS_MEDIA_STREAM (self), FALSE);
915 
916   return priv->volume;
917 }
918 
919 /**
920  * gtk_media_stream_set_volume: (attributes org.gtk.Method.set_property=volume)
921  * @self: a `GtkMediaStream`
922  * @volume: New volume of the stream from 0.0 to 1.0
923  *
924  * Sets the volume of the audio stream.
925  *
926  * This function call will work even if the stream is muted.
927  *
928  * The given @volume should range from 0.0 for silence to 1.0
929  * for as loud as possible. Values outside of this range will
930  * be clamped to the nearest value.
931  *
932  * If the stream has no audio or is muted, calling this function
933  * will still work but it will not have an immediate audible effect.
934  * When the stream is unmuted, the new volume setting will take effect.
935  */
936 void
gtk_media_stream_set_volume(GtkMediaStream * self,double volume)937 gtk_media_stream_set_volume (GtkMediaStream *self,
938                              double          volume)
939 {
940   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
941 
942   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
943 
944   volume = CLAMP (volume, 0.0, 1.0);
945 
946   if (priv->volume == volume)
947     return;
948 
949   priv->volume = volume;
950 
951   GTK_MEDIA_STREAM_GET_CLASS (self)->update_audio (self, priv->muted, priv->volume);
952 
953   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VOLUME]);
954 }
955 
956 /**
957  * gtk_media_stream_realize:
958  * @self: a `GtkMediaStream`
959  * @surface: a `GdkSurface`
960  *
961  * Called by users to attach the media stream to a `GdkSurface` they manage.
962  *
963  * The stream can then access the resources of @surface for its
964  * rendering purposes. In particular, media streams might want to
965  * create a `GdkGLContext` or sync to the `GdkFrameClock`.
966  *
967  * Whoever calls this function is responsible for calling
968  * [method@Gtk.MediaStream.unrealize] before either the stream
969  * or @surface get destroyed.
970  *
971  * Multiple calls to this function may happen from different
972  * users of the video, even with the same @surface. Each of these
973  * calls must be followed by its own call to
974  * [method@Gtk.MediaStream.unrealize].
975  *
976  * It is not required to call this function to make a media stream work.
977  */
978 void
gtk_media_stream_realize(GtkMediaStream * self,GdkSurface * surface)979 gtk_media_stream_realize (GtkMediaStream *self,
980                           GdkSurface      *surface)
981 {
982   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
983   g_return_if_fail (GDK_IS_SURFACE (surface));
984 
985   g_object_ref (self);
986   g_object_ref (surface);
987 
988   GTK_MEDIA_STREAM_GET_CLASS (self)->realize (self, surface);
989 }
990 
991 /**
992  * gtk_media_stream_unrealize:
993  * @self: a `GtkMediaStream` previously realized
994  * @surface: the `GdkSurface` the stream was realized with
995  *
996  * Undoes a previous call to gtk_media_stream_realize().
997  *
998  * This causes the stream to release all resources it had
999  * allocated from @surface.
1000  */
1001 void
gtk_media_stream_unrealize(GtkMediaStream * self,GdkSurface * surface)1002 gtk_media_stream_unrealize (GtkMediaStream *self,
1003                             GdkSurface      *surface)
1004 {
1005   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1006   g_return_if_fail (GDK_IS_SURFACE (surface));
1007 
1008   GTK_MEDIA_STREAM_GET_CLASS (self)->unrealize (self, surface);
1009 
1010   g_object_unref (surface);
1011   g_object_unref (self);
1012 }
1013 
1014 /**
1015  * gtk_media_stream_stream_prepared:
1016  * @self: a `GtkMediaStream`
1017  * @has_audio: %TRUE if the stream should advertise audio support
1018  * @has_video: %TRUE if the stream should advertise video support
1019  * @seekable: %TRUE if the stream should advertise seekability
1020  * @duration: The duration of the stream or 0 if unknown
1021  *
1022  * Called by `GtkMediaStream` implementations to advertise the stream
1023  * being ready to play and providing details about the stream.
1024  *
1025  * Note that the arguments are hints. If the stream implementation
1026  * cannot determine the correct values, it is better to err on the
1027  * side of caution and return %TRUE. User interfaces will use those
1028  * values to determine what controls to show.
1029  *
1030  * This function may not be called again until the stream has been
1031  * reset via [method@Gtk.MediaStream.stream_unprepared].
1032  *
1033  * Since: 4.4
1034  */
1035 void
gtk_media_stream_stream_prepared(GtkMediaStream * self,gboolean has_audio,gboolean has_video,gboolean seekable,gint64 duration)1036 gtk_media_stream_stream_prepared (GtkMediaStream *self,
1037                                   gboolean        has_audio,
1038                                   gboolean        has_video,
1039                                   gboolean        seekable,
1040                                   gint64          duration)
1041 {
1042   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1043 
1044   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1045   g_return_if_fail (!gtk_media_stream_is_prepared (self));
1046 
1047   g_object_freeze_notify (G_OBJECT (self));
1048 
1049   if (priv->has_audio != has_audio)
1050     {
1051       priv->has_audio = has_audio;
1052       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_AUDIO]);
1053     }
1054   if (priv->has_video != has_video)
1055     {
1056       priv->has_video = has_video;
1057       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_VIDEO]);
1058     }
1059   if (priv->seekable != seekable)
1060     {
1061       priv->seekable = seekable;
1062       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKABLE]);
1063     }
1064   if (priv->duration != duration)
1065     {
1066       priv->duration = duration;
1067       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
1068     }
1069 
1070   priv->prepared = TRUE;
1071   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
1072 
1073   g_object_thaw_notify (G_OBJECT (self));
1074 }
1075 
1076 /**
1077  * gtk_media_stream_stream_unprepared:
1078  * @self: a `GtkMediaStream`
1079  *
1080  * Resets a given media stream implementation.
1081  *
1082  * [method@Gtk.MediaStream.stream_prepared] can then be called again.
1083  *
1084  * This function will also reset any error state the stream was in.
1085  *
1086  * Since: 4.4
1087  */
1088 void
gtk_media_stream_stream_unprepared(GtkMediaStream * self)1089 gtk_media_stream_stream_unprepared (GtkMediaStream *self)
1090 {
1091   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1092 
1093   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1094   g_return_if_fail (gtk_media_stream_is_prepared (self));
1095 
1096   g_object_freeze_notify (G_OBJECT (self));
1097 
1098   gtk_media_stream_pause (self);
1099 
1100   if (priv->has_audio)
1101     {
1102       priv->has_audio = FALSE;
1103       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_AUDIO]);
1104     }
1105   if (priv->has_video)
1106     {
1107       priv->has_video = FALSE;
1108       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_VIDEO]);
1109     }
1110   if (priv->seekable)
1111     {
1112       priv->seekable = FALSE;
1113       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKABLE]);
1114     }
1115   if (priv->seeking)
1116     {
1117       priv->seeking = FALSE;
1118       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
1119     }
1120   if (priv->duration != 0)
1121     {
1122       priv->duration = 0;
1123       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
1124     }
1125   if (priv->timestamp != 0)
1126     {
1127       priv->timestamp = 0;
1128       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]);
1129     }
1130   if (priv->error)
1131     {
1132       g_clear_error (&priv->error);
1133       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
1134     }
1135 
1136   priv->prepared = FALSE;
1137   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
1138 
1139   g_object_thaw_notify (G_OBJECT (self));
1140 }
1141 
1142 /**
1143  * gtk_media_stream_prepared: (skip)
1144  * @self: a `GtkMediaStream`
1145  * @has_audio: %TRUE if the stream should advertise audio support
1146  * @has_video: %TRUE if the stream should advertise video support
1147  * @seekable: %TRUE if the stream should advertise seekability
1148  * @duration: The duration of the stream or 0 if unknown
1149  *
1150  * Same as gtk_media_stream_stream_prepared().
1151  *
1152  * Deprecated: 4.4: Use [method@Gtk.MediaStream.stream_prepared] instead.
1153  */
1154 void
gtk_media_stream_prepared(GtkMediaStream * self,gboolean has_audio,gboolean has_video,gboolean seekable,gint64 duration)1155 gtk_media_stream_prepared (GtkMediaStream *self,
1156                            gboolean        has_audio,
1157                            gboolean        has_video,
1158                            gboolean        seekable,
1159                            gint64          duration)
1160 {
1161   gtk_media_stream_stream_prepared (self, has_audio, has_video, seekable, duration);
1162 }
1163 
1164 /**
1165  * gtk_media_stream_unprepared: (skip)
1166  * @self: a `GtkMediaStream`
1167  *
1168  * Same as gtk_media_stream_stream_unprepared().
1169  *
1170  * Deprecated: 4.4: Use [method@Gtk.MediaStream.stream_unprepared] instead.
1171  */
1172 void
gtk_media_stream_unprepared(GtkMediaStream * self)1173 gtk_media_stream_unprepared (GtkMediaStream *self)
1174 {
1175   gtk_media_stream_stream_unprepared (self);
1176 }
1177 
1178 /**
1179  * gtk_media_stream_gerror:
1180  * @self: a `GtkMediaStream`
1181  * @error: (transfer full): the `GError` to set
1182  *
1183  * Sets @self into an error state.
1184  *
1185  * This will pause the stream (you can check for an error
1186  * via [method@Gtk.MediaStream.get_error] in your
1187  * GtkMediaStream.pause() implementation), abort pending
1188  * seeks and mark the stream as prepared.
1189  *
1190  * if the stream is already in an error state, this call
1191  * will be ignored and the existing error will be retained.
1192  *
1193  * To unset an error, the stream must be reset via a call to
1194  * [method@Gtk.MediaStream.unprepared].
1195  */
1196 void
gtk_media_stream_gerror(GtkMediaStream * self,GError * error)1197 gtk_media_stream_gerror (GtkMediaStream *self,
1198                          GError         *error)
1199 {
1200   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1201 
1202   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1203   g_return_if_fail (error != NULL);
1204 
1205   if (priv->error)
1206     {
1207       g_error_free (error);
1208       return;
1209     }
1210 
1211   g_object_freeze_notify (G_OBJECT (self));
1212 
1213   priv->error = error;
1214 
1215   gtk_media_stream_pause (self);
1216 
1217   if (!priv->prepared)
1218     {
1219       priv->prepared = TRUE;
1220       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]);
1221     }
1222 
1223   if (priv->seeking)
1224     gtk_media_stream_seek_failed (self);
1225 
1226   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
1227 
1228   g_object_thaw_notify (G_OBJECT (self));
1229 }
1230 
1231 /**
1232  * gtk_media_stream_error:
1233  * @self: a `GtkMediaStream`
1234  * @domain: error domain
1235  * @code: error code
1236  * @format: printf()-style format for error message
1237  * @...: parameters for message format
1238  *
1239  * Sets @self into an error state using a printf()-style format string.
1240  *
1241  * This is a utility function that calls [method@Gtk.MediaStream.gerror].
1242  * See that function for details.
1243  */
1244 void
gtk_media_stream_error(GtkMediaStream * self,GQuark domain,int code,const char * format,...)1245 gtk_media_stream_error (GtkMediaStream *self,
1246                         GQuark          domain,
1247                         int             code,
1248                         const char     *format,
1249                         ...)
1250 {
1251   GError *error;
1252   va_list args;
1253 
1254   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1255   g_return_if_fail (domain != 0);
1256   g_return_if_fail (format != NULL);
1257 
1258   va_start (args, format);
1259   error = g_error_new_valist (domain, code, format, args);
1260   va_end (args);
1261 
1262   gtk_media_stream_gerror (self, error);
1263 }
1264 
1265 /**
1266  * gtk_media_stream_error_valist:
1267  * @self: a `GtkMediaStream`
1268  * @domain: error domain
1269  * @code: error code
1270  * @format: printf()-style format for error message
1271  * @args: `va_list` of parameters for the message format
1272  *
1273  * Sets @self into an error state using a printf()-style format string.
1274  *
1275  * This is a utility function that calls [method@Gtk.MediaStream.gerror].
1276  * See that function for details.
1277  */
1278 void
gtk_media_stream_error_valist(GtkMediaStream * self,GQuark domain,int code,const char * format,va_list args)1279 gtk_media_stream_error_valist (GtkMediaStream *self,
1280                                GQuark          domain,
1281                                int             code,
1282                                const char     *format,
1283                                va_list         args)
1284 {
1285   GError *error;
1286 
1287   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1288   g_return_if_fail (domain != 0);
1289   g_return_if_fail (format != NULL);
1290 
1291   error = g_error_new_valist (domain, code, format, args);
1292 
1293   gtk_media_stream_gerror (self, error);
1294 }
1295 
1296 /**
1297  * gtk_media_stream_update:
1298  * @self: a `GtkMediaStream`
1299  * @timestamp: the new timestamp
1300  *
1301  * Media stream implementations should regularly call this
1302  * function to update the timestamp reported by the stream.
1303  *
1304  * It is up to implementations to call this at the frequency
1305  * they deem appropriate.
1306  *
1307  * The media stream must be prepared when this function is called.
1308  */
1309 void
gtk_media_stream_update(GtkMediaStream * self,gint64 timestamp)1310 gtk_media_stream_update (GtkMediaStream *self,
1311                          gint64          timestamp)
1312 {
1313   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1314 
1315   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1316   g_return_if_fail (gtk_media_stream_is_prepared (self));
1317 
1318   g_object_freeze_notify (G_OBJECT (self));
1319 
1320   /* Timestamp before duration is important here.
1321    * This way the duration notify will be emitted first which will
1322    * make GtkMediaControls update adjustment->upper so that the
1323    * timestamp notify will cause the timestamp to not be clamped.
1324    */
1325   if (priv->timestamp != timestamp)
1326     {
1327       priv->timestamp = timestamp;
1328       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]);
1329     }
1330   if (priv->duration > 0 && timestamp > priv->duration)
1331     {
1332       priv->duration = priv->timestamp;
1333       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
1334     }
1335 
1336   g_object_thaw_notify (G_OBJECT (self));
1337 }
1338 
1339 /**
1340  * gtk_media_stream_stream_ended:
1341  * @self: a `GtkMediaStream`
1342  *
1343  * Pauses the media stream and marks it as ended.
1344  *
1345  * This is a hint only, calls to [method@Gtk.MediaStream.play]
1346  * may still happen.
1347  *
1348  * The media stream must be prepared when this function is called.
1349  *
1350  * Since: 4.4
1351  */
1352 void
gtk_media_stream_stream_ended(GtkMediaStream * self)1353 gtk_media_stream_stream_ended (GtkMediaStream *self)
1354 {
1355   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1356 
1357   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1358   g_return_if_fail (gtk_media_stream_is_prepared (self));
1359   g_return_if_fail (!gtk_media_stream_get_ended (self));
1360 
1361   g_object_freeze_notify (G_OBJECT (self));
1362 
1363   gtk_media_stream_pause (self);
1364 
1365   priv->ended = TRUE;
1366   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
1367 
1368   g_object_thaw_notify (G_OBJECT (self));
1369 }
1370 
1371 /**
1372  * gtk_media_stream_ended: (skip)
1373  * @self: a `GtkMediaStream`
1374  *
1375  * Pauses the media stream and marks it as ended.
1376  *
1377  * This is a hint only, calls to [method@Gtk.MediaStream.play]
1378  * may still happen.
1379  *
1380  * The media stream must be prepared when this function is called.
1381  *
1382  * Deprecated: 4.4: Use [method@Gtk.MediaStream.stream_ended] instead
1383  */
1384 void
gtk_media_stream_ended(GtkMediaStream * self)1385 gtk_media_stream_ended (GtkMediaStream *self)
1386 {
1387   gtk_media_stream_stream_ended (self);
1388 }
1389 
1390 /**
1391  * gtk_media_stream_seek_success:
1392  * @self: a `GtkMediaStream`
1393  *
1394  * Ends a seek operation started via GtkMediaStream.seek() successfully.
1395  *
1396  * This function will unset the GtkMediaStream:ended property
1397  * if it was set.
1398  *
1399  * See [method@Gtk.MediaStream.seek_failed] for the other way of
1400  * ending a seek.
1401  */
1402 void
gtk_media_stream_seek_success(GtkMediaStream * self)1403 gtk_media_stream_seek_success (GtkMediaStream *self)
1404 {
1405   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1406 
1407   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1408   g_return_if_fail (gtk_media_stream_is_seeking (self));
1409 
1410   g_object_freeze_notify (G_OBJECT (self));
1411 
1412   priv->seeking = FALSE;
1413   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
1414 
1415   if (priv->ended)
1416     {
1417       priv->ended = FALSE;
1418       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENDED]);
1419     }
1420 
1421   g_object_thaw_notify (G_OBJECT (self));
1422 }
1423 
1424 /**
1425  * gtk_media_stream_seek_failed:
1426  * @self: a `GtkMediaStream`
1427  *
1428  * Ends a seek operation started via GtkMediaStream.seek() as a failure.
1429  *
1430  * This will not cause an error on the stream and will assume that
1431  * playback continues as if no seek had happened.
1432  *
1433  * See [method@Gtk.MediaStream.seek_success] for the other way of
1434  * ending a seek.
1435  */
1436 void
gtk_media_stream_seek_failed(GtkMediaStream * self)1437 gtk_media_stream_seek_failed (GtkMediaStream *self)
1438 {
1439   GtkMediaStreamPrivate *priv = gtk_media_stream_get_instance_private (self);
1440 
1441   g_return_if_fail (GTK_IS_MEDIA_STREAM (self));
1442   g_return_if_fail (gtk_media_stream_is_seeking (self));
1443 
1444   priv->seeking = FALSE;
1445 
1446   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEEKING]);
1447 }
1448