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 "gtkgstmediafileprivate.h"
23#include "gtkgstpaintableprivate.h"
24
25#include <gst/player/gstplayer.h>
26#include <gst/player/gstplayer-g-main-context-signal-dispatcher.h>
27
28struct _GtkGstMediaFile
29{
30 GtkMediaFile parent_instance;
31
32 GstPlayer *player;
33 GdkPaintable *paintable;
34};
35
36struct _GtkGstMediaFileClass
37{
38 GtkMediaFileClass parent_class;
39};
40
41#define TO_GST_TIME(ts) ((ts) * (GST_SECOND / G_USEC_PER_SEC))
42#define FROM_GST_TIME(ts) ((ts) / (GST_SECOND / G_USEC_PER_SEC))
43
44static void
45gtk_gst_media_file_paintable_snapshot (GdkPaintable *paintable,
46 GdkSnapshot *snapshot,
47 double width,
48 double height)
49{
50 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: paintable);
51
52 gdk_paintable_snapshot (paintable: self->paintable, snapshot, width, height);
53}
54
55static GdkPaintable *
56gtk_gst_media_file_paintable_get_current_image (GdkPaintable *paintable)
57{
58 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: paintable);
59
60 return gdk_paintable_get_current_image (paintable: self->paintable);
61}
62
63static int
64gtk_gst_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
65{
66 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: paintable);
67
68 return gdk_paintable_get_intrinsic_width (paintable: self->paintable);
69}
70
71static int
72gtk_gst_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
73{
74 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: paintable);
75
76 return gdk_paintable_get_intrinsic_height (paintable: self->paintable);
77}
78
79static double gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
80{
81 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: paintable);
82
83 return gdk_paintable_get_intrinsic_aspect_ratio (paintable: self->paintable);
84};
85
86static void
87gtk_gst_media_file_paintable_init (GdkPaintableInterface *iface)
88{
89 iface->snapshot = gtk_gst_media_file_paintable_snapshot;
90 iface->get_current_image = gtk_gst_media_file_paintable_get_current_image;
91 iface->get_intrinsic_width = gtk_gst_media_file_paintable_get_intrinsic_width;
92 iface->get_intrinsic_height = gtk_gst_media_file_paintable_get_intrinsic_height;
93 iface->get_intrinsic_aspect_ratio = gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio;
94}
95
96G_DEFINE_TYPE_EXTENDED (GtkGstMediaFile, gtk_gst_media_file, GTK_TYPE_MEDIA_FILE, 0,
97 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
98 gtk_gst_media_file_paintable_init))
99
100void
101g_io_module_load (GIOModule *module)
102{
103 g_type_module_use (G_TYPE_MODULE (module));
104
105 g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
106 GTK_TYPE_GST_MEDIA_FILE,
107 extension_name: "gstreamer",
108 priority: 10);
109}
110
111G_GNUC_NORETURN
112void
113g_io_module_unload (GIOModule *module)
114{
115 g_assert_not_reached ();
116}
117
118char **
119g_io_module_query (void)
120{
121 char *eps[] = {
122 (char *) GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
123 NULL
124 };
125
126 return g_strdupv (str_array: eps);
127}
128
129static void
130gtk_gst_media_file_ensure_prepared (GtkGstMediaFile *self)
131{
132 GstPlayerMediaInfo *media_info;
133
134 if (gtk_media_stream_is_prepared (self: GTK_MEDIA_STREAM (ptr: self)))
135 return;
136
137 media_info = gst_player_get_media_info (player: self->player);
138 if (media_info)
139 {
140 GstClockTime duration = gst_player_media_info_get_duration (info: media_info);
141
142 gtk_media_stream_stream_prepared (self: GTK_MEDIA_STREAM (ptr: self),
143 has_audio: gst_player_media_info_get_audio_streams (info: media_info) != NULL,
144 has_video: gst_player_media_info_get_video_streams (info: media_info) != NULL,
145 seekable: gst_player_media_info_is_seekable (info: media_info),
146 duration: duration == GST_CLOCK_TIME_NONE ? 0 : FROM_GST_TIME (duration));
147
148 g_object_unref (object: media_info);
149 }
150 else
151 {
152 /* Assuming everything exists is better for the user than pretending it doesn't exist.
153 * Better to be able to control non-existing audio than not be able to control existing audio.
154 *
155 * Only for seeking we can't do a thing, because with 0 duration we can't seek anywhere.
156 */
157 gtk_media_stream_stream_prepared (self: GTK_MEDIA_STREAM (ptr: self),
158 TRUE,
159 TRUE,
160 FALSE,
161 duration: 0);
162 }
163}
164
165static void
166gtk_gst_media_file_position_updated_cb (GstPlayer *player,
167 GstClockTime time,
168 GtkGstMediaFile *self)
169{
170 gtk_gst_media_file_ensure_prepared (self);
171
172 gtk_media_stream_update (self: GTK_MEDIA_STREAM (ptr: self), FROM_GST_TIME (time));
173}
174
175static void
176gtk_gst_media_file_seek_done_cb (GstPlayer *player,
177 GstClockTime time,
178 GtkGstMediaFile *self)
179{
180 /* if we're not seeking, we're doing the loop seek-back after EOS */
181 if (gtk_media_stream_is_seeking (self: GTK_MEDIA_STREAM (ptr: self)))
182 gtk_media_stream_seek_success (self: GTK_MEDIA_STREAM (ptr: self));
183 gtk_media_stream_update (self: GTK_MEDIA_STREAM (ptr: self), FROM_GST_TIME (time));
184}
185
186static void
187gtk_gst_media_file_error_cb (GstPlayer *player,
188 GError *error,
189 GtkGstMediaFile *self)
190{
191 if (gtk_media_stream_get_error (self: GTK_MEDIA_STREAM (ptr: self)))
192 return;
193
194 gtk_media_stream_gerror (self: GTK_MEDIA_STREAM (ptr: self),
195 error: g_error_copy (error));
196}
197
198static void
199gtk_gst_media_file_end_of_stream_cb (GstPlayer *player,
200 GtkGstMediaFile *self)
201{
202 gtk_gst_media_file_ensure_prepared (self);
203
204 if (gtk_media_stream_get_ended (self: GTK_MEDIA_STREAM (ptr: self)))
205 return;
206
207 if (gtk_media_stream_get_loop (self: GTK_MEDIA_STREAM (ptr: self)))
208 {
209 gst_player_seek (player: self->player, position: 0);
210 return;
211 }
212
213 gtk_media_stream_stream_ended (self: GTK_MEDIA_STREAM (ptr: self));
214}
215
216static void
217gtk_gst_media_file_destroy_player (GtkGstMediaFile *self)
218{
219 if (self->player == NULL)
220 return;
221
222 g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_position_updated_cb, self);
223 g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_end_of_stream_cb, self);
224 g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_seek_done_cb, self);
225 g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_error_cb, self);
226 g_object_unref (object: self->player);
227 self->player = NULL;
228}
229
230static void
231gtk_gst_media_file_create_player (GtkGstMediaFile *file)
232{
233 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: file);
234
235 if (self->player != NULL)
236 return;
237
238 self->player = gst_player_new (GST_PLAYER_VIDEO_RENDERER (g_object_ref (self->paintable)),
239 signal_dispatcher: gst_player_g_main_context_signal_dispatcher_new (NULL));
240 g_signal_connect (self->player, "position-updated", G_CALLBACK (gtk_gst_media_file_position_updated_cb), self);
241 g_signal_connect (self->player, "end-of-stream", G_CALLBACK (gtk_gst_media_file_end_of_stream_cb), self);
242 g_signal_connect (self->player, "seek-done", G_CALLBACK (gtk_gst_media_file_seek_done_cb), self);
243 g_signal_connect (self->player, "error", G_CALLBACK (gtk_gst_media_file_error_cb), self);
244}
245
246static void
247gtk_gst_media_file_open (GtkMediaFile *media_file)
248{
249 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: media_file);
250 GFile *file;
251
252 gtk_gst_media_file_create_player (file: self);
253
254 file = gtk_media_file_get_file (self: media_file);
255
256 if (file)
257 {
258 /* XXX: This is technically incorrect because GFile uris aren't real uris */
259 char *uri = g_file_get_uri (file);
260
261 gst_player_set_uri (player: self->player, uri);
262
263 g_free (mem: uri);
264 }
265 else
266 {
267 /* It's an input stream */
268 g_assert_not_reached ();
269 }
270
271 gst_player_pause (player: self->player);
272}
273
274static void
275gtk_gst_media_file_close (GtkMediaFile *file)
276{
277 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: file);
278
279 gtk_gst_media_file_destroy_player (self);
280}
281
282static gboolean
283gtk_gst_media_file_play (GtkMediaStream *stream)
284{
285 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
286
287 if (self->player == NULL)
288 return FALSE;
289
290 gst_player_play (player: self->player);
291
292 return TRUE;
293}
294
295static void
296gtk_gst_media_file_pause (GtkMediaStream *stream)
297{
298 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
299
300 gst_player_pause (player: self->player);
301}
302
303static void
304gtk_gst_media_file_seek (GtkMediaStream *stream,
305 gint64 timestamp)
306{
307 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
308
309 gst_player_seek (player: self->player, TO_GST_TIME (timestamp));
310}
311
312static void
313gtk_gst_media_file_update_audio (GtkMediaStream *stream,
314 gboolean muted,
315 double volume)
316{
317 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
318
319 gst_player_set_mute (player: self->player, val: muted);
320 gst_player_set_volume (player: self->player, val: volume * volume * volume);
321}
322
323static void
324gtk_gst_media_file_realize (GtkMediaStream *stream,
325 GdkSurface *surface)
326{
327 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
328
329 gtk_gst_paintable_realize (self: GTK_GST_PAINTABLE (ptr: self->paintable), surface);
330}
331
332static void
333gtk_gst_media_file_unrealize (GtkMediaStream *stream,
334 GdkSurface *surface)
335{
336 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: stream);
337
338 gtk_gst_paintable_unrealize (self: GTK_GST_PAINTABLE (ptr: self->paintable), surface);
339}
340
341static void
342gtk_gst_media_file_dispose (GObject *object)
343{
344 GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (ptr: object);
345
346 gtk_gst_media_file_destroy_player (self);
347 if (self->paintable)
348 {
349 g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self);
350 g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self);
351 g_clear_object (&self->paintable);
352 }
353
354 G_OBJECT_CLASS (gtk_gst_media_file_parent_class)->dispose (object);
355}
356
357static void
358gtk_gst_media_file_class_init (GtkGstMediaFileClass *klass)
359{
360 GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (ptr: klass);
361 GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (ptr: klass);
362 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
363
364 file_class->open = gtk_gst_media_file_open;
365 file_class->close = gtk_gst_media_file_close;
366
367 stream_class->play = gtk_gst_media_file_play;
368 stream_class->pause = gtk_gst_media_file_pause;
369 stream_class->seek = gtk_gst_media_file_seek;
370 stream_class->update_audio = gtk_gst_media_file_update_audio;
371 stream_class->realize = gtk_gst_media_file_realize;
372 stream_class->unrealize = gtk_gst_media_file_unrealize;
373
374 gobject_class->dispose = gtk_gst_media_file_dispose;
375}
376
377static void
378gtk_gst_media_file_init (GtkGstMediaFile *self)
379{
380 self->paintable = gtk_gst_paintable_new ();
381 g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
382 g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
383}
384
385

source code of gtk/modules/media/gtkgstmediafile.c