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 | |
54 | struct _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 | |
73 | enum |
74 | { |
75 | PROP_0, |
76 | PROP_AUTOPLAY, |
77 | PROP_FILE, |
78 | PROP_LOOP, |
79 | PROP_MEDIA_STREAM, |
80 | |
81 | N_PROPS |
82 | }; |
83 | |
84 | G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET) |
85 | |
86 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
87 | |
88 | static gboolean |
89 | gtk_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 | |
103 | static void |
104 | gtk_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 | |
114 | static void |
115 | gtk_video_motion (GtkEventControllerMotion *motion, |
116 | double x, |
117 | double y, |
118 | GtkVideo *self) |
119 | { |
120 | gtk_video_reveal_controls (self); |
121 | } |
122 | |
123 | static void |
124 | gtk_video_pressed (GtkVideo *self) |
125 | { |
126 | gtk_video_reveal_controls (self); |
127 | } |
128 | |
129 | static void |
130 | gtk_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 | |
148 | static void |
149 | gtk_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 | |
167 | static void |
168 | gtk_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 | |
180 | static void |
181 | gtk_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 | |
195 | static void |
196 | gtk_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 | |
206 | static void |
207 | gtk_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 | |
217 | static void |
218 | gtk_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 | |
230 | static void |
231 | gtk_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 | |
262 | static void |
263 | gtk_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 | |
294 | static void |
295 | gtk_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 | |
374 | static void |
375 | gtk_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 | */ |
387 | GtkWidget * |
388 | gtk_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 | */ |
401 | GtkWidget * |
402 | gtk_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 | */ |
419 | GtkWidget * |
420 | gtk_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 | */ |
440 | GtkWidget * |
441 | gtk_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 | */ |
470 | GtkWidget * |
471 | gtk_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 | */ |
509 | GtkMediaStream * |
510 | gtk_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 | |
517 | static void |
518 | gtk_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 | |
539 | static void |
540 | gtk_video_update_ended (GtkVideo *self) |
541 | { |
542 | gtk_video_update_overlay_icon (self); |
543 | } |
544 | |
545 | static void |
546 | gtk_video_update_error (GtkVideo *self) |
547 | { |
548 | gtk_video_update_overlay_icon (self); |
549 | } |
550 | |
551 | static void |
552 | gtk_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 | |
564 | static void |
565 | gtk_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 | |
572 | static void |
573 | gtk_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 | */ |
606 | void |
607 | gtk_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 | */ |
672 | GFile * |
673 | gtk_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 | */ |
687 | void |
688 | gtk_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 | */ |
736 | void |
737 | gtk_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 | */ |
764 | void |
765 | gtk_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 | */ |
803 | gboolean |
804 | gtk_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 | */ |
819 | void |
820 | gtk_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 | */ |
841 | gboolean |
842 | gtk_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 | */ |
856 | void |
857 | gtk_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 | |