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 "gtkmediafileprivate.h"
23
24#include "gtkdebug.h"
25#include "gtkintl.h"
26#include "gtkmodulesprivate.h"
27#include "gtknomediafileprivate.h"
28
29/**
30 * GtkMediaFile:
31 *
32 * `GtkMediaFile` implements `GtkMediaStream` for files.
33 *
34 * This provides a simple way to play back video files with GTK.
35 *
36 * GTK provides a GIO extension point for `GtkMediaFile` implementations
37 * to allow for external implementations using various media frameworks.
38 *
39 * GTK itself includes implementations using GStreamer and ffmpeg.
40 */
41
42typedef struct _GtkMediaFilePrivate GtkMediaFilePrivate;
43
44struct _GtkMediaFilePrivate
45{
46 GFile *file;
47 GInputStream *input_stream;
48};
49
50enum {
51 PROP_0,
52 PROP_FILE,
53 PROP_INPUT_STREAM,
54
55 N_PROPS,
56};
57
58static GParamSpec *properties[N_PROPS] = { NULL, };
59
60G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMediaFile, gtk_media_file, GTK_TYPE_MEDIA_STREAM,
61 G_ADD_PRIVATE (GtkMediaFile))
62
63#define GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
64 g_critical ("Media file of type '%s' does not implement GtkMediaFile::" # method, G_OBJECT_TYPE_NAME (obj))
65
66static void
67gtk_media_file_default_open (GtkMediaFile *self)
68{
69 GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD (self, open);
70}
71
72static void
73gtk_media_file_default_close (GtkMediaFile *self)
74{
75 gtk_media_stream_stream_unprepared (self: GTK_MEDIA_STREAM (ptr: self));
76}
77
78static void
79gtk_media_file_set_property (GObject *object,
80 guint prop_id,
81 const GValue *value,
82 GParamSpec *pspec)
83
84{
85 GtkMediaFile *self = GTK_MEDIA_FILE (ptr: object);
86
87 switch (prop_id)
88 {
89 case PROP_FILE:
90 gtk_media_file_set_file (self, file: g_value_get_object (value));
91 break;
92
93 case PROP_INPUT_STREAM:
94 gtk_media_file_set_input_stream (self, stream: g_value_get_object (value));
95 break;
96
97 default:
98 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99 break;
100 }
101}
102
103static void
104gtk_media_file_get_property (GObject *object,
105 guint prop_id,
106 GValue *value,
107 GParamSpec *pspec)
108{
109 GtkMediaFile *self = GTK_MEDIA_FILE (ptr: object);
110 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
111
112 switch (prop_id)
113 {
114 case PROP_FILE:
115 g_value_set_object (value, v_object: priv->file);
116 break;
117
118 case PROP_INPUT_STREAM:
119 g_value_set_object (value, v_object: priv->input_stream);
120 break;
121
122 default:
123 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
124 break;
125 }
126}
127
128static void
129gtk_media_file_dispose (GObject *object)
130{
131 GtkMediaFile *self = GTK_MEDIA_FILE (ptr: object);
132 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
133
134 g_clear_object (&priv->file);
135 g_clear_object (&priv->input_stream);
136
137 G_OBJECT_CLASS (gtk_media_file_parent_class)->dispose (object);
138}
139
140static void
141gtk_media_file_class_init (GtkMediaFileClass *class)
142{
143 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
144
145 class->open = gtk_media_file_default_open;
146 class->close = gtk_media_file_default_close;
147
148 gobject_class->set_property = gtk_media_file_set_property;
149 gobject_class->get_property = gtk_media_file_get_property;
150 gobject_class->dispose = gtk_media_file_dispose;
151
152 /**
153 * GtkMediaFile:file: (attributes org.gtk.Property.get=gtk_media_file_get_file org.gtk.Property.set=gtk_media_file_set_file)
154 *
155 * The file being played back or %NULL if not playing a file.
156 */
157 properties[PROP_FILE] =
158 g_param_spec_object (name: "file",
159 P_("File"),
160 P_("File being played back"),
161 G_TYPE_FILE,
162 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
163
164 /**
165 * GtkMediaFile:input-stream: (attributes org.gtk.Property.get=gtk_media_file_get_input_stream org.gtk.Property.set=gtk_media_file_set_input_stream)
166 *
167 * The stream being played back or %NULL if not playing a stream.
168 *
169 * This is %NULL when playing a file.
170 */
171 properties[PROP_INPUT_STREAM] =
172 g_param_spec_object (name: "input-stream",
173 P_("Input stream"),
174 P_("Input stream being played back"),
175 G_TYPE_INPUT_STREAM,
176 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
177
178 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
179}
180
181static void
182gtk_media_file_init (GtkMediaFile *self)
183{
184}
185
186GIOExtension *
187gtk_media_file_get_extension (void)
188{
189 const char *extension_name;
190 GIOExtension *e;
191 GIOExtensionPoint *ep;
192
193 GTK_NOTE (MODULES, g_print ("Looking up MediaFile extension\n"));
194
195 ep = g_io_extension_point_lookup (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
196 e = NULL;
197
198 extension_name = g_getenv (variable: "GTK_MEDIA");
199 if (extension_name)
200 {
201 if (g_str_equal (v1: extension_name, v2: "help"))
202 {
203 GList *l;
204
205 g_print (format: "Supported arguments for GTK_MEDIA environment variable:\n");
206
207 for (l = g_io_extension_point_get_extensions (extension_point: ep); l; l = l->next)
208 {
209 e = l->data;
210
211 g_print (format: "%10s - %d\n", g_io_extension_get_name (extension: e), g_io_extension_get_priority (extension: e));
212 }
213
214 e = NULL;
215 }
216 else
217 {
218 e = g_io_extension_point_get_extension_by_name (extension_point: ep, name: extension_name);
219 if (e == NULL)
220 {
221 g_warning ("Media extension \"%s\" from GTK_MEDIA environment variable not found.", extension_name);
222 }
223 }
224 }
225
226 if (e == NULL)
227 {
228 GList *l = g_io_extension_point_get_extensions (extension_point: ep);
229
230 if (l == NULL)
231 {
232 g_error ("GTK was run without any GtkMediaFile extension being present. This must not happen.");
233 }
234
235 e = l->data;
236 }
237
238 return e;
239}
240
241static GType
242gtk_media_file_get_impl_type (void)
243{
244 static GType impl_type = G_TYPE_NONE;
245 GIOExtension *e;
246
247 if (G_LIKELY (impl_type != G_TYPE_NONE))
248 return impl_type;
249
250 e = gtk_media_file_get_extension ();
251 impl_type = g_io_extension_get_type (extension: e);
252
253 GTK_NOTE (MODULES, g_print ("Using %s from \"%s\" extension\n", g_type_name (impl_type), g_io_extension_get_name (e)));
254
255 return impl_type;
256}
257
258/**
259 * gtk_media_file_new:
260 *
261 * Creates a new empty media file.
262 *
263 * Returns: (type Gtk.MediaFile): a new `GtkMediaFile`
264 **/
265GtkMediaStream *
266gtk_media_file_new (void)
267{
268 return g_object_new (object_type: gtk_media_file_get_impl_type (), NULL);
269}
270
271/**
272 * gtk_media_file_new_for_filename:
273 * @filename: (type filename): filename to open
274 *
275 * Creates a new media file for the given filename.
276 *
277 * This is a utility function that converts the given @filename
278 * to a `GFile` and calls [ctor@Gtk.MediaFile.new_for_file].
279 *
280 * Returns: (type Gtk.MediaFile): a new `GtkMediaFile` playing @filename
281 */
282GtkMediaStream *
283gtk_media_file_new_for_filename (const char *filename)
284{
285 GtkMediaStream *result;
286 GFile *file;
287
288 if (filename)
289 file = g_file_new_for_path (path: filename);
290 else
291 file = NULL;
292
293 result = gtk_media_file_new_for_file (file);
294
295 if (file)
296 g_object_unref (object: file);
297
298 return result;
299}
300
301/**
302 * gtk_media_file_new_for_resource:
303 * @resource_path: resource path to open
304 *
305 * Creates a new new media file for the given resource.
306 *
307 * This is a utility function that converts the given @resource
308 * to a `GFile` and calls [ctor@Gtk.MediaFile.new_for_file].
309 *
310 * Returns: (type Gtk.MediaFile): a new `GtkMediaFile` playing @resource_path
311 */
312GtkMediaStream *
313gtk_media_file_new_for_resource (const char *resource_path)
314{
315 GtkMediaStream *result;
316 GFile *file;
317
318 if (resource_path)
319 {
320 char *uri, *escaped;
321
322 escaped = g_uri_escape_string (unescaped: resource_path,
323 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
324 uri = g_strconcat (string1: "resource://", escaped, NULL);
325 g_free (mem: escaped);
326
327 file = g_file_new_for_uri (uri);
328 g_free (mem: uri);
329 }
330 else
331 {
332 file = NULL;
333 }
334
335 result = gtk_media_file_new_for_file (file);
336
337 if (file)
338 g_object_unref (object: file);
339
340 return result;
341}
342
343/**
344 * gtk_media_file_new_for_file:
345 * @file: The file to play
346 *
347 * Creates a new media file to play @file.
348 *
349 * Returns: (type Gtk.MediaFile): a new `GtkMediaFile` playing @file
350 */
351GtkMediaStream *
352gtk_media_file_new_for_file (GFile *file)
353{
354 g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
355
356 return g_object_new (object_type: gtk_media_file_get_impl_type (),
357 first_property_name: "file", file,
358 NULL);
359}
360
361/**
362 * gtk_media_file_new_for_input_stream:
363 * @stream: The stream to play
364 *
365 * Creates a new media file to play @stream.
366 *
367 * If you want the resulting media to be seekable,
368 * the stream should implement the `GSeekable` interface.
369 *
370 * Returns: (type Gtk.MediaFile): a new `GtkMediaFile`
371 */
372GtkMediaStream *
373gtk_media_file_new_for_input_stream (GInputStream *stream)
374{
375 g_return_val_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream), NULL);
376
377 return g_object_new (object_type: gtk_media_file_get_impl_type (),
378 first_property_name: "input-stream", stream,
379 NULL);
380}
381
382static gboolean
383gtk_media_file_is_open (GtkMediaFile *self)
384{
385 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
386
387 return priv->file || priv->input_stream;
388}
389
390/**
391 * gtk_media_file_clear:
392 * @self: a `GtkMediaFile`
393 *
394 * Resets the media file to be empty.
395 */
396void
397gtk_media_file_clear (GtkMediaFile *self)
398{
399 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
400
401 g_return_if_fail (GTK_IS_MEDIA_FILE (self));
402
403 if (!gtk_media_file_is_open (self))
404 return;
405
406 GTK_MEDIA_FILE_GET_CLASS (ptr: self)->close (self);
407
408 if (priv->input_stream)
409 {
410 g_clear_object (&priv->input_stream);
411 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_INPUT_STREAM]);
412 }
413 if (priv->file)
414 {
415 g_clear_object (&priv->file);
416 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILE]);
417 }
418}
419
420/**
421 * gtk_media_file_set_filename:
422 * @self: a `GtkMediaFile`
423 * @filename: (type filename) (nullable): name of file to play
424 *
425 * Sets the `GtkMediaFile to play the given file.
426 *
427 * This is a utility function that converts the given @filename
428 * to a `GFile` and calls [method@Gtk.MediaFile.set_file].
429 **/
430void
431gtk_media_file_set_filename (GtkMediaFile *self,
432 const char *filename)
433{
434 GFile *file;
435
436 g_return_if_fail (GTK_IS_MEDIA_FILE (self));
437
438 if (filename)
439 file = g_file_new_for_path (path: filename);
440 else
441 file = NULL;
442
443 gtk_media_file_set_file (self, file);
444
445 if (file)
446 g_object_unref (object: file);
447}
448
449/**
450 * gtk_media_file_set_resource:
451 * @self: a `GtkMediaFile`
452 * @resource_path: (nullable): path to resource to play
453 *
454 * Sets the `GtkMediaFile to play the given resource.
455 *
456 * This is a utility function that converts the given @resource_path
457 * to a `GFile` and calls [method@Gtk.MediaFile.set_file].
458 */
459void
460gtk_media_file_set_resource (GtkMediaFile *self,
461 const char *resource_path)
462{
463 GFile *file;
464
465 g_return_if_fail (GTK_IS_MEDIA_FILE (self));
466
467 if (resource_path)
468 {
469 char *uri, *escaped;
470
471 escaped = g_uri_escape_string (unescaped: resource_path,
472 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
473 uri = g_strconcat (string1: "resource://", escaped, NULL);
474 g_free (mem: escaped);
475
476 file = g_file_new_for_uri (uri);
477 g_free (mem: uri);
478 }
479 else
480 {
481 file = NULL;
482 }
483
484
485 gtk_media_file_set_file (self, file);
486
487 if (file)
488 g_object_unref (object: file);
489}
490
491/**
492 * gtk_media_file_set_file: (attributes org.gtk.Method.set_property=file)
493 * @self: a `GtkMediaFile`
494 * @file: (nullable): the file to play
495 *
496 * Sets the `GtkMediaFile` to play the given file.
497 *
498 * If any file is still playing, stop playing it.
499 */
500void
501gtk_media_file_set_file (GtkMediaFile *self,
502 GFile *file)
503{
504 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
505
506 g_return_if_fail (GTK_IS_MEDIA_FILE (self));
507 g_return_if_fail (file == NULL || G_IS_FILE (file));
508
509 if (file)
510 g_object_ref (file);
511
512 g_object_freeze_notify (G_OBJECT (self));
513
514 gtk_media_file_clear (self);
515
516 if (file)
517 {
518 priv->file = file;
519 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILE]);
520
521 GTK_MEDIA_FILE_GET_CLASS (ptr: self)->open (self);
522 }
523
524 g_object_thaw_notify (G_OBJECT (self));
525}
526
527/**
528 * gtk_media_file_get_file: (attributes org.gtk.Method.get_property=file)
529 * @self: a `GtkMediaFile`
530 *
531 * Returns the file that @self is currently playing from.
532 *
533 * When @self is not playing or not playing from a file,
534 * %NULL is returned.
535 *
536 * Returns: (nullable) (transfer none): The currently playing file
537 */
538GFile *
539gtk_media_file_get_file (GtkMediaFile *self)
540{
541 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
542
543 g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
544
545 return priv->file;
546}
547
548/**
549 * gtk_media_file_set_input_stream: (attributes org.gtk.Method.set_property=input-stream)
550 * @self: a `GtkMediaFile`
551 * @stream: (nullable): the stream to play from
552 *
553 * Sets the `GtkMediaFile` to play the given stream.
554 *
555 * If anything is still playing, stop playing it.
556 *
557 * Full control about the @stream is assumed for the duration of
558 * playback. The stream will not be closed.
559 */
560void
561gtk_media_file_set_input_stream (GtkMediaFile *self,
562 GInputStream *stream)
563{
564 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
565
566 g_return_if_fail (GTK_IS_MEDIA_FILE (self));
567 g_return_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream));
568
569 if (stream)
570 g_object_ref (stream);
571
572 g_object_freeze_notify (G_OBJECT (self));
573
574 gtk_media_file_clear (self);
575
576 if (stream)
577 {
578 priv->input_stream = stream;
579 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_INPUT_STREAM]);
580
581 GTK_MEDIA_FILE_GET_CLASS (ptr: self)->open (self);
582 }
583
584 g_object_thaw_notify (G_OBJECT (self));
585}
586
587/**
588 * gtk_media_file_get_input_stream: (attributes org.gtk.Method.get_property=input-stream)
589 * @self: a `GtkMediaFile`
590 *
591 * Returns the stream that @self is currently playing from.
592 *
593 * When @self is not playing or not playing from a stream,
594 * %NULL is returned.
595 *
596 * Returns: (nullable) (transfer none): The currently playing stream
597 */
598GInputStream *
599gtk_media_file_get_input_stream (GtkMediaFile *self)
600{
601 GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
602
603 g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
604
605 return priv->input_stream;
606}
607
608void
609gtk_media_file_extension_init (void)
610{
611 GIOExtensionPoint *ep;
612 GIOModuleScope *scope;
613 char **paths;
614 int i;
615
616 GTK_NOTE (MODULES,
617 g_print ("Registering extension point %s\n", GTK_MEDIA_FILE_EXTENSION_POINT_NAME));
618
619 ep = g_io_extension_point_register (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
620 g_io_extension_point_set_required_type (extension_point: ep, GTK_TYPE_MEDIA_FILE);
621
622 g_type_ensure (GTK_TYPE_NO_MEDIA_FILE);
623
624 scope = g_io_module_scope_new (flags: G_IO_MODULE_SCOPE_BLOCK_DUPLICATES);
625
626 paths = _gtk_get_module_path (type: "media");
627 for (i = 0; paths[i]; i++)
628 {
629 GTK_NOTE (MODULES,
630 g_print ("Scanning io modules in %s\n", paths[i]));
631 g_io_modules_scan_all_in_directory_with_scope (dirname: paths[i], scope);
632 }
633 g_strfreev (str_array: paths);
634
635 g_io_module_scope_free (scope);
636
637 if (GTK_DEBUG_CHECK (MODULES))
638 {
639 GList *list, *l;
640
641 list = g_io_extension_point_get_extensions (extension_point: ep);
642 for (l = list; l; l = l->next)
643 {
644 GIOExtension *ext = l->data;
645 g_print (format: "extension: %s: type %s\n",
646 g_io_extension_get_name (extension: ext),
647 g_type_name (type: g_io_extension_get_type (extension: ext)));
648 }
649 }
650}
651

source code of gtk/gtk/gtkmediafile.c