1 /* ev-media-player.h
2 * this file is part of evince, a gnome document viewer
3 *
4 * Copyright (C) 2015 Igalia S.L.
5 *
6 * Evince is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Evince is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #include "ev-media-player.h"
24
25 #include <gst/video/videooverlay.h>
26
27 #if defined (GDK_WINDOWING_X11)
28 #include <gdk/gdkx.h>
29 #elif defined (GDK_WINDOWING_WIN32)
30 #include <gdk/gdkwin32.h>
31 #endif
32
33 enum {
34 PROP_0,
35 PROP_MEDIA,
36 };
37
38 struct _EvMediaPlayer {
39 GtkBox parent;
40
41 EvMedia *media;
42 GtkWidget *drawing_area;
43 GtkWidget *controls;
44 GtkWidget *play_button;
45 GtkWidget *slider;
46
47 GstElement *pipeline;
48 GstBus *bus;
49 GstVideoOverlay *overlay;
50 guint64 window_handle;
51 gboolean is_playing;
52 gboolean is_seeking;
53 gdouble duration;
54 gdouble position;
55
56 guint position_timeout_id;
57 };
58
59 struct _EvMediaPlayerClass {
60 GtkBoxClass parent_class;
61 };
62
G_DEFINE_TYPE(EvMediaPlayer,ev_media_player,GTK_TYPE_BOX)63 G_DEFINE_TYPE (EvMediaPlayer, ev_media_player, GTK_TYPE_BOX)
64
65 static void
66 ev_media_player_update_position (EvMediaPlayer *player)
67 {
68 if (!ev_media_get_show_controls (player->media))
69 return;
70
71 gtk_range_set_value (GTK_RANGE (player->slider), player->position);
72 }
73
74 static gboolean
query_position_cb(EvMediaPlayer * player)75 query_position_cb (EvMediaPlayer *player)
76 {
77 gint64 position;
78
79 gst_element_query_position (player->pipeline, GST_FORMAT_TIME, &position);
80 player->position = (gdouble)position / GST_SECOND;
81 ev_media_player_update_position (player);
82
83 return G_SOURCE_CONTINUE;
84 }
85
86 static void
ev_media_player_query_position_start(EvMediaPlayer * player)87 ev_media_player_query_position_start (EvMediaPlayer *player)
88 {
89 if (player->position_timeout_id > 0)
90 return;
91
92 if (!ev_media_get_show_controls (player->media))
93 return;
94
95 player->position_timeout_id = g_timeout_add (1000 / 15,
96 (GSourceFunc)query_position_cb,
97 player);
98 }
99
100 static void
ev_media_player_query_position_stop(EvMediaPlayer * player)101 ev_media_player_query_position_stop (EvMediaPlayer *player)
102 {
103 if (player->position_timeout_id > 0) {
104 g_source_remove (player->position_timeout_id);
105 player->position_timeout_id = 0;
106 }
107 }
108
109 static void
ev_media_player_query_position(EvMediaPlayer * player)110 ev_media_player_query_position (EvMediaPlayer *player)
111 {
112 if (!ev_media_get_show_controls (player->media))
113 return;
114
115 if (player->duration <= 0)
116 return;
117
118 if (player->is_playing)
119 ev_media_player_query_position_start (player);
120 else
121 ev_media_player_query_position_stop (player);
122 query_position_cb (player);
123 }
124
125 static void
ev_media_player_update_play_button(EvMediaPlayer * player)126 ev_media_player_update_play_button (EvMediaPlayer *player)
127 {
128 if (!ev_media_get_show_controls (player->media))
129 return;
130
131 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_button_get_image (GTK_BUTTON (player->play_button))),
132 player->is_playing ? "media-playback-pause-symbolic" : "media-playback-start-symbolic",
133 GTK_ICON_SIZE_MENU);
134 }
135
136 static void
ev_media_player_update_state(EvMediaPlayer * player,GstState * new_state)137 ev_media_player_update_state (EvMediaPlayer *player,
138 GstState *new_state)
139 {
140 GstState state, pending;
141 gboolean is_playing;
142
143 gst_element_get_state (player->pipeline, &state, &pending, 250 * GST_NSECOND);
144
145 is_playing = state == GST_STATE_PLAYING;
146 if (is_playing != player->is_playing) {
147 player->is_playing = is_playing;
148 ev_media_player_update_play_button (player);
149 ev_media_player_query_position (player);
150 }
151
152 if (new_state)
153 *new_state = state;
154 }
155
156 static void
ev_media_player_toggle_state(EvMediaPlayer * player)157 ev_media_player_toggle_state (EvMediaPlayer *player)
158 {
159 GstState current, pending, new_state;
160
161 if (!player->pipeline)
162 return;
163
164 gst_element_get_state (player->pipeline, ¤t, &pending, 0);
165 new_state = current == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING;
166 if (pending != new_state)
167 gst_element_set_state (player->pipeline, new_state);
168 }
169
170 static void
ev_media_player_seek(EvMediaPlayer * player,GtkScrollType scroll,gdouble position)171 ev_media_player_seek (EvMediaPlayer *player,
172 GtkScrollType scroll,
173 gdouble position)
174 {
175 if (!player->pipeline)
176 return;
177
178 position = CLAMP (position, 0, player->duration);
179 if (gst_element_seek_simple (player->pipeline,
180 GST_FORMAT_TIME,
181 GST_SEEK_FLAG_FLUSH,
182 (gint64)(position * GST_SECOND))) {
183 player->is_seeking = TRUE;
184 ev_media_player_query_position_stop (player);
185 player->position = position;
186 ev_media_player_update_position (player);
187 }
188 }
189
190
191 static void
ev_media_player_notify_eos(EvMediaPlayer * player)192 ev_media_player_notify_eos (EvMediaPlayer *player)
193 {
194 if (!ev_media_get_show_controls (player->media)) {
195 /* A media without controls can't be played again */
196 gtk_widget_destroy (GTK_WIDGET (player));
197 return;
198 }
199
200 ev_media_player_update_play_button (player);
201 ev_media_player_query_position_stop (player);
202 player->position = 0;
203 ev_media_player_update_position (player);
204 gst_element_set_state (player->pipeline, GST_STATE_READY);
205 }
206
207 static GstBusSyncReply
bus_sync_handle(GstBus * bus,GstMessage * message,EvMediaPlayer * player)208 bus_sync_handle (GstBus *bus,
209 GstMessage *message,
210 EvMediaPlayer *player)
211 {
212 GstVideoOverlay *overlay;
213
214 if (!gst_is_video_overlay_prepare_window_handle_message (message))
215 return GST_BUS_PASS;
216
217 overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message));
218 gst_video_overlay_set_window_handle (overlay, (guintptr)player->window_handle);
219 gst_video_overlay_expose (overlay);
220
221 player->overlay = overlay;
222
223 gst_message_unref (message);
224
225 return GST_BUS_DROP;
226 }
227
228 static void
bus_message_handle(GstBus * bus,GstMessage * message,EvMediaPlayer * player)229 bus_message_handle (GstBus *bus,
230 GstMessage *message,
231 EvMediaPlayer *player)
232 {
233 switch (GST_MESSAGE_TYPE (message)) {
234 case GST_MESSAGE_ERROR: {
235 GError *error = NULL;
236 gchar *dbg;
237
238 gst_message_parse_error (message, &error, &dbg);
239 g_warning ("Error: %s (%s)\n", error->message, dbg);
240 g_error_free (error);
241 g_free (dbg);
242 }
243 break;
244 case GST_MESSAGE_STATE_CHANGED:
245 if (GST_MESSAGE_SRC (message) != (GstObject *)player->pipeline)
246 return;
247
248 if (!player->is_seeking)
249 ev_media_player_update_state (player, NULL);
250
251 break;
252 case GST_MESSAGE_ASYNC_DONE: {
253 GstState state;
254
255 if (GST_MESSAGE_SRC (message) != (GstObject *)player->pipeline)
256 return;
257
258 if (!ev_media_get_show_controls (player->media))
259 return;
260
261 if (player->is_seeking) {
262 player->is_seeking = FALSE;
263 if (player->is_playing)
264 ev_media_player_query_position_start (player);
265 } else {
266 ev_media_player_update_state (player, &state);
267
268 if (player->duration == 0 && (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)) {
269 gint64 duration;
270
271 gst_element_query_duration (player->pipeline, GST_FORMAT_TIME, &duration);
272 player->duration = (gdouble)duration / GST_SECOND;
273 gtk_range_set_range (GTK_RANGE (player->slider), 0, player->duration);
274 }
275 }
276
277 }
278 break;
279 case GST_MESSAGE_EOS:
280 player->is_playing = FALSE;
281 ev_media_player_notify_eos (player);
282
283 break;
284 default:
285 break;
286 }
287 }
288
289 static void
drawing_area_realize_cb(GtkWidget * widget,EvMediaPlayer * player)290 drawing_area_realize_cb (GtkWidget *widget,
291 EvMediaPlayer *player)
292 {
293 #if defined (GDK_WINDOWING_X11)
294 player->window_handle = (guint64)GDK_WINDOW_XID (gtk_widget_get_window (widget));
295 #elif defined (GDK_WINDOWING_WIN32)
296 player->window_handle = (guint64)GDK_WINDOW_HWND (gtk_widget_get_window (widget));
297 #else
298 g_assert_not_reached ();
299 #endif
300 }
301
302 static void
ev_media_player_size_allocate(GtkWidget * widget,GtkAllocation * allocation)303 ev_media_player_size_allocate (GtkWidget *widget,
304 GtkAllocation *allocation)
305 {
306 EvMediaPlayer *player = EV_MEDIA_PLAYER (widget);
307 GdkRectangle controls_allocation;
308
309 GTK_WIDGET_CLASS (ev_media_player_parent_class)->size_allocate (widget, allocation);
310
311 if (!ev_media_get_show_controls (player->media))
312 return;
313
314 /* Give all the allocated size to the drawing area */
315 gtk_widget_size_allocate (player->drawing_area, allocation);
316
317 /* And give space for the controls below */
318 controls_allocation.x = allocation->x;
319 controls_allocation.y = allocation->y + allocation->height;
320 controls_allocation.width = allocation->width;
321 controls_allocation.height = gtk_widget_get_allocated_height (player->controls);
322 gtk_widget_size_allocate (player->controls, &controls_allocation);
323
324 allocation->height += controls_allocation.height;
325
326 gtk_widget_set_allocation (widget, allocation);
327 }
328
329 static void
ev_media_player_dispose(GObject * object)330 ev_media_player_dispose (GObject *object)
331 {
332 EvMediaPlayer *player = EV_MEDIA_PLAYER (object);
333
334 ev_media_player_query_position_stop (player);
335
336 if (player->bus) {
337 gst_bus_remove_signal_watch (player->bus);
338 gst_object_unref (player->bus);
339 player->bus = NULL;
340 }
341
342 if (player->pipeline) {
343 gst_element_set_state (player->pipeline, GST_STATE_NULL);
344 gst_object_unref (player->pipeline);
345 player->pipeline = NULL;
346 }
347
348 g_clear_object (&player->media);
349
350 G_OBJECT_CLASS (ev_media_player_parent_class)->dispose (object);
351 }
352
353 static void
ev_media_player_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)354 ev_media_player_set_property (GObject *object,
355 guint prop_id,
356 const GValue *value,
357 GParamSpec *pspec)
358 {
359 EvMediaPlayer *player = EV_MEDIA_PLAYER (object);
360
361 switch (prop_id) {
362 case PROP_MEDIA:
363 player->media = EV_MEDIA (g_value_dup_object (value));
364 break;
365 default:
366 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
367 }
368 }
369
370 static void
ev_media_player_init(EvMediaPlayer * player)371 ev_media_player_init (EvMediaPlayer *player)
372 {
373 gtk_orientable_set_orientation (GTK_ORIENTABLE (player), GTK_ORIENTATION_VERTICAL);
374
375 player->pipeline = gst_element_factory_make ("playbin", NULL);
376 if (!player->pipeline) {
377 g_warning ("Failed to create playbin\n");
378 return;
379 }
380
381 player->drawing_area = gtk_drawing_area_new ();
382 g_signal_connect (player->drawing_area, "realize",
383 G_CALLBACK (drawing_area_realize_cb),
384 player);
385 gtk_box_pack_start (GTK_BOX (player), player->drawing_area, TRUE, TRUE, 0);
386 gtk_widget_show (player->drawing_area);
387
388 player->bus = gst_pipeline_get_bus (GST_PIPELINE (player->pipeline));
389 gst_bus_set_sync_handler (player->bus, (GstBusSyncHandler)bus_sync_handle, player, NULL);
390 gst_bus_add_signal_watch (player->bus);
391 g_signal_connect_object (player->bus, "message",
392 G_CALLBACK (bus_message_handle),
393 player, 0);
394 }
395
396 static void
ev_media_player_setup_media_controls(EvMediaPlayer * player)397 ev_media_player_setup_media_controls (EvMediaPlayer *player)
398 {
399 GtkAdjustment *adjustment;
400 GtkCssProvider *provider;
401
402 player->controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
403
404 gtk_style_context_add_class (gtk_widget_get_style_context (player->controls), GTK_STYLE_CLASS_OSD);
405
406 player->play_button = gtk_button_new ();
407 g_signal_connect_swapped (player->play_button, "clicked",
408 G_CALLBACK (ev_media_player_toggle_state),
409 player);
410 gtk_widget_set_name (player->play_button, "ev-media-player-play-button");
411 gtk_widget_set_valign (player->play_button, GTK_ALIGN_CENTER);
412 gtk_button_set_relief (GTK_BUTTON (player->play_button), GTK_RELIEF_NONE);
413 gtk_button_set_image (GTK_BUTTON (player->play_button),
414 gtk_image_new_from_icon_name ("media-playback-start-symbolic",
415 GTK_ICON_SIZE_MENU));
416 gtk_button_set_label(GTK_BUTTON (player->play_button), NULL);
417 gtk_widget_set_focus_on_click (player->play_button, FALSE);
418
419 provider = gtk_css_provider_new ();
420 gtk_css_provider_load_from_data (provider, "#ev-media-player-play-button { padding: 0px 8px 0px 8px; }", -1, NULL);
421 gtk_style_context_add_provider (gtk_widget_get_style_context (player->play_button),
422 GTK_STYLE_PROVIDER (provider),
423 GTK_STYLE_PROVIDER_PRIORITY_USER);
424 g_object_unref (provider);
425
426 gtk_box_pack_start (GTK_BOX (player->controls), player->play_button, FALSE, TRUE, 0);
427 gtk_widget_show (player->play_button);
428
429 adjustment = gtk_adjustment_new (0, 0, 1, 0.1, 0.10, 0);
430 player->slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
431 g_signal_connect_swapped (player->slider, "change-value",
432 G_CALLBACK (ev_media_player_seek),
433 player);
434 gtk_widget_set_hexpand (player->slider, TRUE);
435 gtk_scale_set_draw_value (GTK_SCALE (player->slider), FALSE);
436 gtk_box_pack_start (GTK_BOX (player->controls), player->slider, FALSE, TRUE, 0);
437 gtk_widget_show (player->slider);
438
439 gtk_box_pack_start (GTK_BOX (player), player->controls, FALSE, FALSE, 0);
440 gtk_widget_show (player->controls);
441 }
442
443 static void
ev_media_player_constructed(GObject * object)444 ev_media_player_constructed (GObject *object)
445 {
446 EvMediaPlayer *player = EV_MEDIA_PLAYER (object);
447
448 G_OBJECT_CLASS (ev_media_player_parent_class)->constructed (object);
449
450 if (ev_media_get_show_controls (player->media))
451 ev_media_player_setup_media_controls (player);
452
453 if (!player->pipeline)
454 return;
455
456 g_object_set (player->pipeline, "uri", ev_media_get_uri (player->media), NULL);
457 gst_element_set_state (player->pipeline, GST_STATE_PLAYING);
458 }
459
460 static void
ev_media_player_class_init(EvMediaPlayerClass * klass)461 ev_media_player_class_init (EvMediaPlayerClass *klass)
462 {
463 GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
464 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
465
466 if (!gst_is_initialized ()) {
467 GError *error = NULL;
468
469 if (!gst_init_check (NULL, NULL, &error)) {
470 g_warning ("Failed to initialize GStreamer: %s\n", error->message);
471 g_error_free (error);
472 }
473 }
474
475 g_object_class->constructed = ev_media_player_constructed;
476 g_object_class->dispose = ev_media_player_dispose;
477 g_object_class->set_property = ev_media_player_set_property;
478 widget_class->size_allocate = ev_media_player_size_allocate;
479
480 g_object_class_install_property (g_object_class,
481 PROP_MEDIA,
482 g_param_spec_object ("media",
483 "Media",
484 "The media played by the player",
485 EV_TYPE_MEDIA,
486 G_PARAM_WRITABLE |
487 G_PARAM_CONSTRUCT_ONLY |
488 G_PARAM_STATIC_STRINGS));
489 }
490
491 GtkWidget *
ev_media_player_new(EvMedia * media)492 ev_media_player_new (EvMedia *media)
493 {
494 g_return_val_if_fail (EV_IS_MEDIA (media), NULL);
495
496 return GTK_WIDGET (g_object_new (EV_TYPE_MEDIA_PLAYER, "media", media, NULL));
497 }
498
499 EvMedia *
ev_media_player_get_media(EvMediaPlayer * player)500 ev_media_player_get_media (EvMediaPlayer *player)
501 {
502 g_return_val_if_fail (EV_IS_MEDIA_PLAYER (player), NULL);
503
504 return player->media;
505 }
506