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 "gtkvideo.h"
23
24#include "gtkbinlayout.h"
25#include "gtkeventcontrollermotion.h"
26#include "gtkimage.h"
27#include "gtkintl.h"
28#include "gtkmediacontrols.h"
29#include "gtkmediafile.h"
30#include "gtknative.h"
31#include "gtkpicture.h"
32#include "gtkrevealer.h"
33#include "gtkwidgetprivate.h"
34
35/**
36 * GtkVideo:
37 *
38 * `GtkVideo` is a widget to show a `GtkMediaStream` with media controls.
39 *
40 * ![An example GtkVideo](video.png)
41 *
42 * The controls are available separately as [class@Gtk.MediaControls].
43 * If you just want to display a video without controls, you can treat it
44 * like any other paintable and for example put it into a [class@Gtk.Picture].
45 *
46 * `GtkVideo` aims to cover use cases such as previews, embedded animations,
47 * etc. It supports autoplay, looping, and simple media controls. It does
48 * not have support for video overlays, multichannel audio, device
49 * selection, or input. If you are writing a full-fledged video player,
50 * you may want to use the [iface@Gdk.Paintable] API and a media framework
51 * such as Gstreamer directly.
52 */
53
54struct _GtkVideo
55{
56 GtkWidget parent_instance;
57
58 GFile *file;
59 GtkMediaStream *media_stream;
60
61 GtkWidget *box;
62 GtkWidget *video_picture;
63 GtkWidget *overlay_icon;
64 GtkWidget *controls_revealer;
65 GtkWidget *controls;
66 guint controls_hide_source;
67
68 guint autoplay : 1;
69 guint loop : 1;
70 guint grabbed : 1;
71};
72
73enum
74{
75 PROP_0,
76 PROP_AUTOPLAY,
77 PROP_FILE,
78 PROP_LOOP,
79 PROP_MEDIA_STREAM,
80
81 N_PROPS
82};
83
84G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET)
85
86static GParamSpec *properties[N_PROPS] = { NULL, };
87
88static gboolean
89gtk_video_hide_controls (gpointer data)
90{
91 GtkVideo *self = data;
92
93 if (self->grabbed)
94 return G_SOURCE_CONTINUE;
95
96 gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
97
98 self->controls_hide_source = 0;
99
100 return G_SOURCE_REMOVE;
101}
102
103static void
104gtk_video_reveal_controls (GtkVideo *self)
105{
106 gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), TRUE);
107 if (self->controls_hide_source)
108 g_source_remove (tag: self->controls_hide_source);
109 self->controls_hide_source = g_timeout_add (interval: 5 * 1000,
110 function: gtk_video_hide_controls,
111 data: self);
112}
113
114static void
115gtk_video_motion (GtkEventControllerMotion *motion,
116 double x,
117 double y,
118 GtkVideo *self)
119{
120 gtk_video_reveal_controls (self);
121}
122
123static void
124gtk_video_pressed (GtkVideo *self)
125{
126 gtk_video_reveal_controls (self);
127}
128
129static void
130gtk_video_realize (GtkWidget *widget)
131{
132 GtkVideo *self = GTK_VIDEO (ptr: widget);
133
134 GTK_WIDGET_CLASS (gtk_video_parent_class)->realize (widget);
135
136 if (self->media_stream)
137 {
138 GdkSurface *surface;
139
140 surface = gtk_native_get_surface (self: gtk_widget_get_native (widget));
141 gtk_media_stream_realize (self: self->media_stream, surface);
142 }
143
144 if (self->file)
145 gtk_media_file_set_file (self: GTK_MEDIA_FILE (ptr: self->media_stream), file: self->file);
146}
147
148static void
149gtk_video_unrealize (GtkWidget *widget)
150{
151 GtkVideo *self = GTK_VIDEO (ptr: widget);
152
153 if (self->autoplay && self->media_stream)
154 gtk_media_stream_pause (self: self->media_stream);
155
156 if (self->media_stream)
157 {
158 GdkSurface *surface;
159
160 surface = gtk_native_get_surface (self: gtk_widget_get_native (widget));
161 gtk_media_stream_unrealize (self: self->media_stream, surface);
162 }
163
164 GTK_WIDGET_CLASS (gtk_video_parent_class)->unrealize (widget);
165}
166
167static void
168gtk_video_map (GtkWidget *widget)
169{
170 GtkVideo *self = GTK_VIDEO (ptr: widget);
171
172 GTK_WIDGET_CLASS (gtk_video_parent_class)->map (widget);
173
174 if (self->autoplay &&
175 self->media_stream &&
176 gtk_media_stream_is_prepared (self: self->media_stream))
177 gtk_media_stream_play (self: self->media_stream);
178}
179
180static void
181gtk_video_unmap (GtkWidget *widget)
182{
183 GtkVideo *self = GTK_VIDEO (ptr: widget);
184
185 if (self->controls_hide_source)
186 {
187 g_source_remove (tag: self->controls_hide_source);
188 self->controls_hide_source = 0;
189 gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
190 }
191
192 GTK_WIDGET_CLASS (gtk_video_parent_class)->unmap (widget);
193}
194
195static void
196gtk_video_hide (GtkWidget *widget)
197{
198 GtkVideo *self = GTK_VIDEO (ptr: widget);
199
200 if (self->autoplay && self->media_stream)
201 gtk_media_stream_pause (self: self->media_stream);
202
203 GTK_WIDGET_CLASS (gtk_video_parent_class)->hide (widget);
204}
205
206static void
207gtk_video_set_focus_child (GtkWidget *widget,
208 GtkWidget *child)
209{
210 GtkVideo *self = GTK_VIDEO (ptr: widget);
211
212 self->grabbed = child != NULL;
213
214 GTK_WIDGET_CLASS (gtk_video_parent_class)->set_focus_child (widget, child);
215}
216
217static void
218gtk_video_dispose (GObject *object)
219{
220 GtkVideo *self = GTK_VIDEO (ptr: object);
221
222 gtk_video_set_media_stream (self, NULL);
223
224 g_clear_pointer (&self->box, gtk_widget_unparent);
225 g_clear_object (&self->file);
226
227 G_OBJECT_CLASS (gtk_video_parent_class)->dispose (object);
228}
229
230static void
231gtk_video_get_property (GObject *object,
232 guint property_id,
233 GValue *value,
234 GParamSpec *pspec)
235{
236 GtkVideo *self = GTK_VIDEO (ptr: object);
237
238 switch (property_id)
239 {
240 case PROP_AUTOPLAY:
241 g_value_set_boolean (value, v_boolean: self->autoplay);
242 break;
243
244 case PROP_FILE:
245 g_value_set_object (value, v_object: self->file);
246 break;
247
248 case PROP_LOOP:
249 g_value_set_boolean (value, v_boolean: self->loop);
250 break;
251
252 case PROP_MEDIA_STREAM:
253 g_value_set_object (value, v_object: self->media_stream);
254 break;
255
256 default:
257 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
258 break;
259 }
260}
261
262static void
263gtk_video_set_property (GObject *object,
264 guint property_id,
265 const GValue *value,
266 GParamSpec *pspec)
267{
268 GtkVideo *self = GTK_VIDEO (ptr: object);
269
270 switch (property_id)
271 {
272 case PROP_AUTOPLAY:
273 gtk_video_set_autoplay (self, autoplay: g_value_get_boolean (value));
274 break;
275
276 case PROP_FILE:
277 gtk_video_set_file (self, file: g_value_get_object (value));
278 break;
279
280 case PROP_LOOP:
281 gtk_video_set_loop (self, loop: g_value_get_boolean (value));
282 break;
283
284 case PROP_MEDIA_STREAM:
285 gtk_video_set_media_stream (self, stream: g_value_get_object (value));
286 break;
287
288 default:
289 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290 break;
291 }
292}
293
294static void
295gtk_video_class_init (GtkVideoClass *klass)
296{
297 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
298 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
299
300 widget_class->realize = gtk_video_realize;
301 widget_class->unrealize = gtk_video_unrealize;
302 widget_class->map = gtk_video_map;
303 widget_class->unmap = gtk_video_unmap;
304 widget_class->hide = gtk_video_hide;
305 widget_class->set_focus_child = gtk_video_set_focus_child;
306
307 gobject_class->dispose = gtk_video_dispose;
308 gobject_class->get_property = gtk_video_get_property;
309 gobject_class->set_property = gtk_video_set_property;
310
311 /**
312 * GtkVideo:autoplay: (attributes org.gtk.Property.get=gtk_video_get_autoplay org.gtk.Property.set=gtk_video_set_autoplay)
313 *
314 * If the video should automatically begin playing.
315 */
316 properties[PROP_AUTOPLAY] =
317 g_param_spec_boolean (name: "autoplay",
318 P_("Autoplay"),
319 P_("If playback should begin automatically"),
320 FALSE,
321 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
322
323 /**
324 * GtkVideo:file: (attributes org.gtk.Property.get=gtk_video_get_file org.gtk.Property.set=gtk_video_set_file)
325 *
326 * The file played by this video if the video is playing a file.
327 */
328 properties[PROP_FILE] =
329 g_param_spec_object (name: "file",
330 P_("File"),
331 P_("The video file played back"),
332 G_TYPE_FILE,
333 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
334
335 /**
336 * GtkVideo:loop: (attributes org.gtk.Property.get=gtk_video_get_loop org.gtk.Property.set=gtk_video_set_loop)
337 *
338 * If new media files should be set to loop.
339 */
340 properties[PROP_LOOP] =
341 g_param_spec_boolean (name: "loop",
342 P_("Loop"),
343 P_("If new media streams should be set to loop"),
344 FALSE,
345 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
346
347 /**
348 * GtkVideo:media-stream: (attributes org.gtk.Property.get=gtk_video_get_media_stream org.gtk.Property.set=gtk_video_set_media_stream)
349 *
350 * The media-stream played
351 */
352 properties[PROP_MEDIA_STREAM] =
353 g_param_spec_object (name: "media-stream",
354 P_("Media Stream"),
355 P_("The media stream played"),
356 GTK_TYPE_MEDIA_STREAM,
357 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
358
359 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
360
361 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtkvideo.ui");
362 gtk_widget_class_bind_template_child (widget_class, GtkVideo, box);
363 gtk_widget_class_bind_template_child (widget_class, GtkVideo, video_picture);
364 gtk_widget_class_bind_template_child (widget_class, GtkVideo, overlay_icon);
365 gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls);
366 gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls_revealer);
367 gtk_widget_class_bind_template_callback (widget_class, gtk_video_motion);
368 gtk_widget_class_bind_template_callback (widget_class, gtk_video_pressed);
369
370 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
371 gtk_widget_class_set_css_name (widget_class, I_("video"));
372}
373
374static void
375gtk_video_init (GtkVideo *self)
376{
377 gtk_widget_init_template (GTK_WIDGET (self));
378}
379
380/**
381 * gtk_video_new:
382 *
383 * Creates a new empty `GtkVideo`.
384 *
385 * Returns: a new `GtkVideo`
386 */
387GtkWidget *
388gtk_video_new (void)
389{
390 return g_object_new (GTK_TYPE_VIDEO, NULL);
391}
392
393/**
394 * gtk_video_new_for_media_stream:
395 * @stream: (nullable): a `GtkMediaStream`
396 *
397 * Creates a `GtkVideo` to play back the given @stream.
398 *
399 * Returns: a new `GtkVideo`
400 */
401GtkWidget *
402gtk_video_new_for_media_stream (GtkMediaStream *stream)
403{
404 g_return_val_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream), NULL);
405
406 return g_object_new (GTK_TYPE_VIDEO,
407 first_property_name: "media-stream", stream,
408 NULL);
409}
410
411/**
412 * gtk_video_new_for_file:
413 * @file: (nullable): a `GFile`
414 *
415 * Creates a `GtkVideo` to play back the given @file.
416 *
417 * Returns: a new `GtkVideo`
418 */
419GtkWidget *
420gtk_video_new_for_file (GFile *file)
421{
422 g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
423
424 return g_object_new (GTK_TYPE_VIDEO,
425 first_property_name: "file", file,
426 NULL);
427}
428
429/**
430 * gtk_video_new_for_filename:
431 * @filename: (nullable) (type filename): filename to play back
432 *
433 * Creates a `GtkVideo` to play back the given @filename.
434 *
435 * This is a utility function that calls [ctor@Gtk.Video.new_for_file],
436 * See that function for details.
437 *
438 * Returns: a new `GtkVideo`
439 */
440GtkWidget *
441gtk_video_new_for_filename (const char *filename)
442{
443 GtkWidget *result;
444 GFile *file;
445
446 if (filename)
447 file = g_file_new_for_path (path: filename);
448 else
449 file = NULL;
450
451 result = gtk_video_new_for_file (file);
452
453 if (file)
454 g_object_unref (object: file);
455
456 return result;
457}
458
459/**
460 * gtk_video_new_for_resource:
461 * @resource_path: (nullable): resource path to play back
462 *
463 * Creates a `GtkVideo` to play back the resource at the
464 * given @resource_path.
465 *
466 * This is a utility function that calls [ctor@Gtk.Video.new_for_file].
467 *
468 * Returns: a new `GtkVideo`
469 */
470GtkWidget *
471gtk_video_new_for_resource (const char *resource_path)
472{
473 GtkWidget *result;
474 GFile *file;
475
476 if (resource_path)
477 {
478 char *uri, *escaped;
479
480 escaped = g_uri_escape_string (unescaped: resource_path,
481 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
482 uri = g_strconcat (string1: "resource://", escaped, NULL);
483 g_free (mem: escaped);
484
485 file = g_file_new_for_uri (uri);
486 g_free (mem: uri);
487 }
488 else
489 {
490 file = NULL;
491 }
492
493 result = gtk_video_new_for_file (file);
494
495 if (file)
496 g_object_unref (object: file);
497
498 return result;
499}
500
501/**
502 * gtk_video_get_media_stream: (attributes org.gtk.Method.get_property=media-stream)
503 * @self: a `GtkVideo`
504 *
505 * Gets the media stream managed by @self or %NULL if none.
506 *
507 * Returns: (nullable) (transfer none): The media stream managed by @self
508 */
509GtkMediaStream *
510gtk_video_get_media_stream (GtkVideo *self)
511{
512 g_return_val_if_fail (GTK_IS_VIDEO (self), NULL);
513
514 return self->media_stream;
515}
516
517static void
518gtk_video_update_overlay_icon (GtkVideo *self)
519{
520 const char *icon_name;
521 const GError *error = NULL;
522
523 if (self->media_stream == NULL)
524 icon_name = "media-eject-symbolic";
525 else if ((error = gtk_media_stream_get_error (self: self->media_stream)))
526 icon_name = "dialog-error-symbolic";
527 else if (gtk_media_stream_get_ended (self: self->media_stream))
528 icon_name = "media-playlist-repeat-symbolic";
529 else
530 icon_name = "media-playback-start-symbolic";
531
532 gtk_image_set_from_icon_name (GTK_IMAGE (self->overlay_icon), icon_name);
533 if (error)
534 gtk_widget_set_tooltip_text (widget: self->overlay_icon, text: error->message);
535 else
536 gtk_widget_set_tooltip_text (widget: self->overlay_icon, NULL);
537}
538
539static void
540gtk_video_update_ended (GtkVideo *self)
541{
542 gtk_video_update_overlay_icon (self);
543}
544
545static void
546gtk_video_update_error (GtkVideo *self)
547{
548 gtk_video_update_overlay_icon (self);
549}
550
551static void
552gtk_video_update_playing (GtkVideo *self)
553{
554 gboolean playing;
555
556 if (self->media_stream != NULL)
557 playing = gtk_media_stream_get_playing (self: self->media_stream);
558 else
559 playing = FALSE;
560
561 gtk_widget_set_visible (widget: self->overlay_icon, visible: !playing);
562}
563
564static void
565gtk_video_update_all (GtkVideo *self)
566{
567 gtk_video_update_ended (self);
568 gtk_video_update_error (self);
569 gtk_video_update_playing (self);
570}
571
572static void
573gtk_video_notify_cb (GtkMediaStream *stream,
574 GParamSpec *pspec,
575 GtkVideo *self)
576{
577 if (g_str_equal (v1: pspec->name, v2: "ended"))
578 gtk_video_update_ended (self);
579 if (g_str_equal (v1: pspec->name, v2: "error"))
580 gtk_video_update_error (self);
581 if (g_str_equal (v1: pspec->name, v2: "playing"))
582 gtk_video_update_playing (self);
583 if (g_str_equal (v1: pspec->name, v2: "prepared"))
584 {
585 if (self->autoplay &&
586 gtk_media_stream_is_prepared (self: stream) &&
587 gtk_widget_get_mapped (GTK_WIDGET (self)))
588 gtk_media_stream_play (self: stream);
589 }
590}
591
592/**
593 * gtk_video_set_media_stream: (attributes org.gtk.Method.set_property=media-stream)
594 * @self: a `GtkVideo`
595 * @stream: (nullable): The media stream to play or %NULL to unset
596 *
597 * Sets the media stream to be played back.
598 *
599 * @self will take full control of managing the media stream. If you
600 * want to manage a media stream yourself, consider using a
601 * [class@Gtk.Picture] for display.
602 *
603 * If you want to display a file, consider using [method@Gtk.Video.set_file]
604 * instead.
605 */
606void
607gtk_video_set_media_stream (GtkVideo *self,
608 GtkMediaStream *stream)
609{
610 g_return_if_fail (GTK_IS_VIDEO (self));
611 g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
612
613 if (self->media_stream == stream)
614 return;
615
616 if (self->media_stream)
617 {
618 if (self->autoplay)
619 gtk_media_stream_pause (self: self->media_stream);
620 g_signal_handlers_disconnect_by_func (self->media_stream,
621 gtk_video_notify_cb,
622 self);
623 if (gtk_widget_get_realized (GTK_WIDGET (self)))
624 {
625 GdkSurface *surface;
626
627 surface = gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self)));
628 gtk_media_stream_unrealize (self: self->media_stream, surface);
629 }
630 g_object_unref (object: self->media_stream);
631 self->media_stream = NULL;
632 }
633
634 if (stream)
635 {
636 self->media_stream = g_object_ref (stream);
637 gtk_media_stream_set_loop (self: stream, loop: self->loop);
638 if (gtk_widget_get_realized (GTK_WIDGET (self)))
639 {
640 GdkSurface *surface;
641
642 surface = gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self)));
643 gtk_media_stream_realize (self: stream, surface);
644 }
645 g_signal_connect (self->media_stream,
646 "notify",
647 G_CALLBACK (gtk_video_notify_cb),
648 self);
649 if (self->autoplay &&
650 gtk_media_stream_is_prepared (self: stream) &&
651 gtk_widget_get_mapped (GTK_WIDGET (self)))
652 gtk_media_stream_play (self: stream);
653 }
654
655 gtk_media_controls_set_media_stream (controls: GTK_MEDIA_CONTROLS (ptr: self->controls), stream);
656 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: self->video_picture), paintable: GDK_PAINTABLE (ptr: stream));
657
658 gtk_video_update_all (self);
659
660 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MEDIA_STREAM]);
661}
662
663/**
664 * gtk_video_get_file: (attributes org.gtk.Method.get_propert=file)
665 * @self: a `GtkVideo`
666 *
667 * Gets the file played by @self or %NULL if not playing back
668 * a file.
669 *
670 * Returns: (nullable) (transfer none): The file played by @self
671 */
672GFile *
673gtk_video_get_file (GtkVideo *self)
674{
675 g_return_val_if_fail (GTK_IS_VIDEO (self), NULL);
676
677 return self->file;
678}
679
680/**
681 * gtk_video_set_file: (attributes org.gtk.Method.set_property=file)
682 * @self: a `GtkVideo`
683 * @file: (nullable): the file to play
684 *
685 * Makes @self play the given @file.
686 */
687void
688gtk_video_set_file (GtkVideo *self,
689 GFile *file)
690{
691 g_return_if_fail (GTK_IS_VIDEO (self));
692 g_return_if_fail (file == NULL || G_IS_FILE (file));
693
694 if (!g_set_object (&self->file, file))
695 return;
696
697 g_object_freeze_notify (G_OBJECT (self));
698
699 if (file)
700 {
701 GtkMediaStream *stream;
702
703 stream = gtk_media_file_new ();
704
705 if (gtk_widget_get_realized (GTK_WIDGET (self)))
706 {
707 GdkSurface *surface;
708
709 surface = gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self)));
710 gtk_media_stream_realize (self: stream, surface);
711 gtk_media_file_set_file (self: GTK_MEDIA_FILE (ptr: stream), file);
712 }
713 gtk_video_set_media_stream (self, stream);
714
715 g_object_unref (object: stream);
716 }
717 else
718 {
719 gtk_video_set_media_stream (self, NULL);
720 }
721
722 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILE]);
723
724 g_object_thaw_notify (G_OBJECT (self));
725}
726
727/**
728 * gtk_video_set_filename:
729 * @self: a `GtkVideo`
730 * @filename: (type filename) (nullable): the filename to play
731 *
732 * Makes @self play the given @filename.
733 *
734 * This is a utility function that calls gtk_video_set_file(),
735 */
736void
737gtk_video_set_filename (GtkVideo *self,
738 const char *filename)
739{
740 GFile *file;
741
742 g_return_if_fail (GTK_IS_VIDEO (self));
743
744 if (filename)
745 file = g_file_new_for_path (path: filename);
746 else
747 file = NULL;
748
749 gtk_video_set_file (self, file);
750
751 if (file)
752 g_object_unref (object: file);
753}
754
755/**
756 * gtk_video_set_resource:
757 * @self: a `GtkVideo`
758 * @resource_path: (nullable): the resource to set
759 *
760 * Makes @self play the resource at the given @resource_path.
761 *
762 * This is a utility function that calls [method@Gtk.Video.set_file].
763 */
764void
765gtk_video_set_resource (GtkVideo *self,
766 const char *resource_path)
767{
768 GFile *file;
769
770 g_return_if_fail (GTK_IS_VIDEO (self));
771
772 if (resource_path)
773 {
774 char *uri, *escaped;
775
776 escaped = g_uri_escape_string (unescaped: resource_path,
777 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
778 uri = g_strconcat (string1: "resource://", escaped, NULL);
779 g_free (mem: escaped);
780
781 file = g_file_new_for_uri (uri);
782 g_free (mem: uri);
783 }
784 else
785 {
786 file = NULL;
787 }
788
789 gtk_video_set_file (self, file);
790
791 if (file)
792 g_object_unref (object: file);
793}
794
795/**
796 * gtk_video_get_autoplay: (attributes org.gtk.Method.get_property=autoplay)
797 * @self: a `GtkVideo`
798 *
799 * Returns %TRUE if videos have been set to loop.
800 *
801 * Returns: %TRUE if streams should autoplay
802 */
803gboolean
804gtk_video_get_autoplay (GtkVideo *self)
805{
806 g_return_val_if_fail (GTK_IS_VIDEO (self), FALSE);
807
808 return self->autoplay;
809}
810
811/**
812 * gtk_video_set_autoplay: (attributes org.gtk.Method.set_property=autoplay)
813 * @self: a `GtkVideo`
814 * @autoplay: whether media streams should autoplay
815 *
816 * Sets whether @self automatically starts playback when it
817 * becomes visible or when a new file gets loaded.
818 */
819void
820gtk_video_set_autoplay (GtkVideo *self,
821 gboolean autoplay)
822{
823 g_return_if_fail (GTK_IS_VIDEO (self));
824
825 if (self->autoplay == autoplay)
826 return;
827
828 self->autoplay = autoplay;
829
830 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_AUTOPLAY]);
831}
832
833/**
834 * gtk_video_get_loop: (attributes org.gtk.Method.get_property=loop)
835 * @self: a `GtkVideo`
836 *
837 * Returns %TRUE if videos have been set to loop.
838 *
839 * Returns: %TRUE if streams should loop
840 */
841gboolean
842gtk_video_get_loop (GtkVideo *self)
843{
844 g_return_val_if_fail (GTK_IS_VIDEO (self), FALSE);
845
846 return self->loop;
847}
848
849/**
850 * gtk_video_set_loop: (attributes org.gtk.Method.set_property=loop)
851 * @self: a `GtkVideo`
852 * @loop: whether media streams should loop
853 *
854 * Sets whether new files loaded by @self should be set to loop.
855 */
856void
857gtk_video_set_loop (GtkVideo *self,
858 gboolean loop)
859{
860 g_return_if_fail (GTK_IS_VIDEO (self));
861
862 if (self->loop == loop)
863 return;
864
865 self->loop = loop;
866
867 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOOP]);
868}
869

source code of gtk/gtk/gtkvideo.c