1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <math.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <gst/gst.h>
26 #include <gthumb.h>
27 #include <extensions/gstreamer_utils/gstreamer-utils.h>
28 #include "actions.h"
29 #include "gth-media-viewer-page.h"
30 #include "preferences.h"
31 
32 
33 #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
34 #define PROGRESS_DELAY 500
35 
36 
37 static void gth_viewer_page_interface_init (GthViewerPageInterface *iface);
38 
39 
40 struct _GthMediaViewerPagePrivate {
41 	GthBrowser     *browser;
42 	GSettings      *settings;
43 	GthFileData    *file_data;
44 	GFileInfo      *updated_info;
45 	GstElement     *playbin;
46 	GtkBuilder     *builder;
47 	GtkWidget      *video_area;
48 	GtkWidget      *audio_area;
49 	GtkWidget      *area_box;
50 	GtkWidget      *area_overlay;
51 	gboolean        fit_if_larger;
52 	gboolean        visible;
53 	gboolean        playing;
54 	gboolean        paused;
55 	gboolean        loop;
56 	gint64          duration;
57 	int             video_fps_n;
58 	int             video_fps_d;
59 	int             video_width;
60 	int             video_height;
61 	gboolean        has_video;
62 	gboolean        has_audio;
63 	gulong          update_progress_id;
64 	gulong          update_volume_id;
65 	gdouble         rate;
66 	GtkWidget      *mediabar;
67 	GtkWidget      *mediabar_revealer;
68 	GdkPixbuf      *icon;
69 	PangoLayout    *caption_layout;
70 	GdkCursor      *cursor;
71 	GdkCursor      *cursor_void;
72 	gboolean        cursor_visible;
73 	GthScreensaver *screensaver;
74 	GtkWidget      *screenshot_button;
75 	GtkWidget      *fit_button;
76 	gboolean        background_painted;
77 };
78 
79 
80 G_DEFINE_TYPE_WITH_CODE (GthMediaViewerPage,
81 			 gth_media_viewer_page,
82 			 G_TYPE_OBJECT,
83 			 G_ADD_PRIVATE (GthMediaViewerPage)
84 			 G_IMPLEMENT_INTERFACE (GTH_TYPE_VIEWER_PAGE,
85 						gth_viewer_page_interface_init))
86 
87 
88 static double default_rates[] = { 0.03, 0.06, 0.12, 0.25, 0.33, 0.50, 0.66, 1.0, 1.50, 2.0, 3.0, 4.0, 8.0, 16.0, 32.0 };
89 
90 
91 static const GActionEntry actions[] = {
92 	{ "video-screenshot", gth_browser_activate_video_screenshot },
93 	{ "toggle-play", gth_browser_activate_toggle_play },
94 	{ "video-zoom-fit", toggle_action_activated, NULL, "true", gth_browser_activate_video_zoom_fit },
95 };
96 
97 
98 static void
_gth_media_viewer_page_update_caption(GthMediaViewerPage * self)99 _gth_media_viewer_page_update_caption (GthMediaViewerPage *self)
100 {
101 	if (self->priv->caption_layout == NULL)
102 		return;
103 
104 	if (self->priv->file_data != NULL) {
105 		GString     *description;
106 		GthMetadata *metadata;
107 
108 		description = g_string_new ("");
109 		metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "general::title");
110 		if (metadata != NULL) {
111 			g_string_append (description, gth_metadata_get_formatted (metadata));
112 			metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "audio-video::general::artist");
113 			if (metadata != NULL) {
114 				g_string_append (description, "\n");
115 				g_string_append (description, gth_metadata_get_formatted (metadata));
116 			}
117 		}
118 		else
119 			g_string_append (description, g_file_info_get_display_name (self->priv->file_data->info));
120 
121 		pango_layout_set_text (self->priv->caption_layout, description->str, -1);
122 
123 		g_string_free (description, TRUE);
124 	}
125 	else
126 		pango_layout_set_text (self->priv->caption_layout, "", -1);
127 
128 	gtk_widget_queue_draw (GTK_WIDGET (self->priv->audio_area));
129 }
130 
131 
132 static void
video_area_realize_cb(GtkWidget * widget,gpointer user_data)133 video_area_realize_cb (GtkWidget *widget,
134 		       gpointer   user_data)
135 {
136 	GthMediaViewerPage *self = user_data;
137 
138 	self->priv->cursor = _gdk_cursor_new_for_widget (widget, GDK_LEFT_PTR);
139 	self->priv->cursor_void = _gdk_cursor_new_for_widget (self->priv->video_area, GDK_BLANK_CURSOR);
140 
141 	if (self->priv->cursor_visible)
142 		gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor);
143 	else
144 		gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor_void);
145 
146 	self->priv->caption_layout = gtk_widget_create_pango_layout (widget, "");
147 	pango_layout_set_alignment (self->priv->caption_layout, PANGO_ALIGN_CENTER);
148 	_gth_media_viewer_page_update_caption (self);
149 
150 	self->priv->background_painted = FALSE;
151 }
152 
153 
154 static void
video_area_unrealize_cb(GtkWidget * widget,gpointer user_data)155 video_area_unrealize_cb (GtkWidget *widget,
156 			 gpointer   user_data)
157 {
158 	GthMediaViewerPage *self = user_data;
159 
160 	if (self->priv->cursor) {
161 		g_object_unref (self->priv->cursor);
162 		self->priv->cursor = NULL;
163 	}
164 
165 	if (self->priv->cursor_void) {
166 		g_object_unref (self->priv->cursor_void);
167 		self->priv->cursor_void = NULL;
168 	}
169 
170 	g_object_unref (self->priv->caption_layout);
171 	self->priv->caption_layout = NULL;
172 }
173 
174 
175 static void
update_zoom_info(GthMediaViewerPage * self)176 update_zoom_info (GthMediaViewerPage *self)
177 {
178 	GtkAllocation  allocation;
179 	double         view_width;
180 	double         view_height;
181 	int            zoom;
182 	char          *text;
183 
184 	if (! self->priv->has_video) {
185 		gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), "");
186 		return;
187 	}
188 
189 	gtk_widget_get_allocation (self->priv->video_area, &allocation);
190 
191 	view_width = allocation.width;
192 	view_height = (((double) self->priv->video_height / self->priv->video_width) * view_width);
193 	if (view_height > allocation.height) {
194 		view_height = allocation.height;
195 		view_width = (((double) self->priv->video_width / self->priv->video_height) * view_height);
196 	}
197 
198 	if (self->priv->video_width > 0)
199 		zoom = (int) round ((double) view_width / self->priv->video_width * 100);
200 	else if (self->priv->video_height > 0)
201 		zoom = (int) round ((double) view_height / self->priv->video_height * 100);
202 	else
203 		zoom = 100;
204 	text = g_strdup_printf ("  %d%%  ", zoom);
205 	gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), text);
206 
207 	g_free (text);
208 }
209 
210 
211 static void
video_area_size_allocate_cb(GtkWidget * widget,GdkRectangle * allocation,gpointer user_data)212 video_area_size_allocate_cb (GtkWidget    *widget,
213 			     GdkRectangle *allocation,
214 			     gpointer      user_data)
215 {
216 	GthMediaViewerPage *self = user_data;
217 	update_zoom_info (self);
218 }
219 
220 
221 static gboolean
video_area_draw_cb(GtkWidget * widget,cairo_t * cr,gpointer user_data)222 video_area_draw_cb (GtkWidget *widget,
223 		    cairo_t   *cr,
224 		    gpointer   user_data)
225 {
226 	GthMediaViewerPage *self = user_data;
227 	GtkAllocation       allocation;
228 	GtkStyleContext    *style_context;
229 
230 	if (self->priv->has_video && self->priv->background_painted)
231 		return FALSE;
232 
233 	gtk_widget_get_allocation (widget, &allocation);
234 	style_context = gtk_widget_get_style_context (widget);
235 
236 	if (self->priv->icon == NULL) {
237 		char  *type;
238 		GIcon *icon;
239 		int    size;
240 
241 		type = NULL;
242 		if (self->priv->file_data != NULL)
243 			type = g_content_type_from_mime_type (gth_file_data_get_mime_type (self->priv->file_data));
244 		if (type == NULL)
245 			type = g_content_type_from_mime_type ("text/plain");
246 		icon = g_content_type_get_icon (type);
247 		size = allocation.width;
248 		if (size > allocation.height)
249 			size = allocation.height;
250 		size = size / 3;
251 		self->priv->icon = _g_icon_get_pixbuf (icon, size, _gtk_widget_get_icon_theme (widget));
252 
253 		g_object_unref (icon);
254 		g_free (type);
255 	}
256 
257 	cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
258 	cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
259 	cairo_fill (cr);
260 
261 	if (self->priv->icon != NULL) {
262 		int                   icon_w, icon_h;
263 		int                   text_w;
264 		int                   icon_x, icon_y;
265 		PangoRectangle        logical_rect;
266 		int                   x, y;
267 		PangoFontDescription *font;
268 
269 		icon_w = gdk_pixbuf_get_width (self->priv->icon);
270 		icon_h = gdk_pixbuf_get_height (self->priv->icon);
271 
272 		text_w = (icon_w * 3 / 2);
273 		pango_layout_set_width (self->priv->caption_layout, PANGO_SCALE * text_w);
274 		pango_layout_get_extents (self->priv->caption_layout, NULL, &logical_rect);
275 
276 		icon_x = (allocation.width - icon_w) / 2;
277 		x = (allocation.width - text_w) / 2;
278 
279 		icon_y = (allocation.height - (icon_h + PANGO_PIXELS (logical_rect.height))) / 2;
280 		y = icon_y + icon_h;
281 
282 		gdk_cairo_set_source_pixbuf (cr, self->priv->icon, icon_x, icon_y);
283 		cairo_rectangle (cr, icon_x, icon_y, icon_w, icon_h);
284 		cairo_fill (cr);
285 
286 		cairo_move_to (cr, x, y);
287 		gtk_style_context_get (style_context, gtk_widget_get_state_flags (widget), "font", &font, NULL);
288 		pango_layout_set_font_description (self->priv->caption_layout, font);
289 		pango_cairo_layout_path (cr, self->priv->caption_layout);
290 		cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
291 		cairo_fill (cr);
292 	}
293 
294 	self->priv->background_painted = TRUE;
295 
296 	return TRUE;
297 }
298 
299 
300 static gboolean
video_area_button_press_cb(GtkWidget * widget,GdkEventButton * event,GthMediaViewerPage * self)301 video_area_button_press_cb (GtkWidget          *widget,
302 			    GdkEventButton     *event,
303 			    GthMediaViewerPage *self)
304 {
305 	if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1) ) {
306 		gtk_button_clicked (GTK_BUTTON (GET_WIDGET ("play_button")));
307 		return TRUE;
308 	}
309 
310 	return gth_browser_viewer_button_press_cb (self->priv->browser, event);
311 }
312 
313 
314 static gboolean
video_area_popup_menu_cb(GtkWidget * widget,GthMediaViewerPage * self)315 video_area_popup_menu_cb (GtkWidget          *widget,
316 			  GthMediaViewerPage *self)
317 {
318 	gth_browser_file_menu_popup (self->priv->browser, NULL);
319 	return TRUE;
320 }
321 
322 
323 static gboolean
video_area_scroll_event_cb(GtkWidget * widget,GdkEventScroll * event,GthMediaViewerPage * self)324 video_area_scroll_event_cb (GtkWidget 	       *widget,
325 			    GdkEventScroll     *event,
326 			    GthMediaViewerPage *self)
327 {
328 	return gth_browser_viewer_scroll_event_cb (self->priv->browser, event);
329 }
330 
331 
332 static void
volume_value_changed_cb(GtkAdjustment * adjustment,gpointer user_data)333 volume_value_changed_cb (GtkAdjustment *adjustment,
334 			 gpointer       user_data)
335 {
336 	GthMediaViewerPage *self = user_data;
337 	double              v;
338 
339 	if (self->priv->playbin == NULL)
340 		return;
341 
342 	/* cubic in [0,1], linear in [1,2] */
343 	v = gtk_adjustment_get_value (adjustment);
344 	if (v <= 1.0)
345 		v = (v * v * v);
346 
347 	g_object_set (self->priv->playbin,
348 		      "volume", v,
349 		      NULL);
350 }
351 
352 
353 static void position_value_changed_cb (GtkAdjustment *adjustment,
354 				       gpointer       user_data);
355 
356 
357 static void
update_current_position_bar(GthMediaViewerPage * self)358 update_current_position_bar (GthMediaViewerPage *self)
359 {
360 	GstFormat format;
361         gint64    current_value = 0;
362 
363         format = GST_FORMAT_TIME;
364         if (gst_element_query_position (self->priv->playbin, format, &current_value)) {
365         	char *s;
366 
367 		if (self->priv->duration <= 0) {
368 			gst_element_query_duration (self->priv->playbin, format, &self->priv->duration);
369 			s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (self->priv->duration));
370 			gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_duration")), s);
371 
372 			g_free (s);
373 		}
374 
375 		/*
376 		g_print ("==> %" G_GINT64_FORMAT " / %" G_GINT64_FORMAT " (%0.3g)\n" ,
377 			 current_value,
378 			 self->priv->duration,
379 			 ((double) current_value / self->priv->duration) * 100.0);
380 		*/
381 
382 		g_signal_handlers_block_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
383 		gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment")), (self->priv->duration > 0) ? ((double) current_value / self->priv->duration) * 100.0 : 0.0);
384 		g_signal_handlers_unblock_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
385 
386 		s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
387 		gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
388 
389 		g_free (s);
390         }
391 }
392 
393 
394 static void
position_value_changed_cb(GtkAdjustment * adjustment,gpointer user_data)395 position_value_changed_cb (GtkAdjustment *adjustment,
396 			   gpointer       user_data)
397 {
398 	GthMediaViewerPage *self = user_data;
399 	gint64              current_value;
400 	char               *s;
401 
402 	if (self->priv->playbin == NULL)
403 		return;
404 
405 	current_value = (gint64) (gtk_adjustment_get_value (adjustment) / 100.0 * self->priv->duration);
406 	gst_element_seek (self->priv->playbin,
407 			  self->priv->rate,
408 			  GST_FORMAT_TIME,
409 			  GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
410 			  GST_SEEK_TYPE_SET,
411 			  current_value,
412 			  GST_SEEK_TYPE_NONE,
413 			  0.0);
414 
415 	s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
416 	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
417 
418 	g_free (s);
419 }
420 
421 
422 static void
update_playback_info(GthMediaViewerPage * self)423 update_playback_info (GthMediaViewerPage *self)
424 {
425 	char *playback_info;
426 
427 	playback_info = g_strdup_printf ("@%2.2f", self->priv->rate);
428 	g_file_info_set_attribute_string (gth_browser_get_current_file (self->priv->browser)->info, "gthumb::statusbar-extra-info", playback_info);
429 	gth_browser_update_statusbar_file_info (self->priv->browser);
430 
431 	g_free (playback_info);
432 }
433 
434 
435 static void
update_player_rate(GthMediaViewerPage * self)436 update_player_rate (GthMediaViewerPage *self)
437 {
438 	gint64 current_value;
439 
440 	self->priv->rate = CLAMP (self->priv->rate,
441 				  default_rates[0],
442 				  default_rates[G_N_ELEMENTS (default_rates) - 1]);
443 
444 	if (self->priv->playbin == NULL)
445 		return;
446 
447 	update_playback_info (self);
448 
449 	if (! self->priv->playing)
450 		return;
451 
452 	current_value = (gint64) (gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment"))) / 100.0 * self->priv->duration);
453 	if (! gst_element_seek (self->priv->playbin,
454 				self->priv->rate,
455 			        GST_FORMAT_TIME,
456 			        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
457 			        GST_SEEK_TYPE_SET,
458 			        current_value,
459 			        GST_SEEK_TYPE_NONE,
460 			        0.0))
461 	{
462 		g_warning ("seek failed");
463 	}
464 }
465 
466 
467 static void
play_button_clicked_cb(GtkButton * button,gpointer user_data)468 play_button_clicked_cb (GtkButton *button,
469 			gpointer   user_data)
470 {
471 	gth_media_viewer_page_toggle_play (GTH_MEDIA_VIEWER_PAGE (user_data));
472 }
473 
474 
475 static int
get_nearest_rate(double rate)476 get_nearest_rate (double rate)
477 {
478 	int    min_idx = -1;
479 	double min_delta = 0;
480 	int    i;
481 
482 	for (i = 0; i < G_N_ELEMENTS (default_rates); i++) {
483 		double delta;
484 
485 		delta = fabs (default_rates[i] - rate);
486 		if ((i == 0) || (delta < min_delta)) {
487 			min_delta = delta;
488 			min_idx = i;
489 		}
490 	}
491 
492 	return min_idx;
493 }
494 
495 
496 static void
play_slower_button_clicked_cb(GtkButton * button,gpointer user_data)497 play_slower_button_clicked_cb (GtkButton *button,
498 			       gpointer   user_data)
499 {
500 	GthMediaViewerPage *self = user_data;
501 	int                 i;
502 
503 	i = get_nearest_rate (self->priv->rate);
504 	if (i > 0)
505 		self->priv->rate = default_rates[i - 1];
506 	else
507 		self->priv->rate = default_rates[0];
508 
509 	update_player_rate (self);
510 }
511 
512 
513 static void
play_faster_button_clicked_cb(GtkButton * button,gpointer user_data)514 play_faster_button_clicked_cb (GtkButton *button,
515 			       gpointer   user_data)
516 {
517 	GthMediaViewerPage *self = user_data;
518 	int                 i;
519 
520 	i = get_nearest_rate (self->priv->rate);
521 	if (i < G_N_ELEMENTS (default_rates) - 1)
522 		self->priv->rate = default_rates[i + 1];
523 	else
524 		self->priv->rate = default_rates[G_N_ELEMENTS (default_rates) - 1];
525 
526 	update_player_rate (self);
527 }
528 
529 
530 static void
loop_button_clicked_cb(GtkButton * button,gpointer user_data)531 loop_button_clicked_cb (GtkButton *button,
532 	 	        gpointer   user_data)
533 {
534 	GthMediaViewerPage *self = user_data;
535 	self->priv->loop = ! self->priv->loop;
536 }
537 
538 
539 static gboolean
update_volume_from_playbin(GthMediaViewerPage * self)540 update_volume_from_playbin (GthMediaViewerPage *self)
541 {
542 	double volume, v;
543 
544 	if (self->priv->update_volume_id != 0) {
545 		g_source_remove (self->priv->update_volume_id);
546 		self->priv->update_volume_id = 0;
547 	}
548 
549 	if ((self->priv->builder == NULL) || (self->priv->playbin == NULL))
550 		return FALSE;
551 
552 	g_object_get (self->priv->playbin, "volume", &volume, NULL);
553 
554 	/* cubic in [0,1], linear in [1,2] */
555 	if (volume <= 1.0)
556 		v = exp (1.0 / 3.0 * log (volume)); /* cube root of volume */
557 	else
558 		v = volume;
559 
560 	g_signal_handlers_block_by_func (GET_WIDGET ("volume_adjustment"), volume_value_changed_cb, self);
561 	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("volume_adjustment")), v);
562 	g_signal_handlers_unblock_by_func (GET_WIDGET ("volume_adjustment"), volume_value_changed_cb, self);
563 
564 	return FALSE;
565 }
566 
567 
568 static gboolean
update_progress_cb(gpointer user_data)569 update_progress_cb (gpointer user_data)
570 {
571 	GthMediaViewerPage *self = user_data;
572 
573         if (self->priv->update_progress_id != 0) {
574                 g_source_remove (self->priv->update_progress_id);
575                 self->priv->update_progress_id = 0;
576         }
577 
578         update_current_position_bar (self);
579 
580         self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
581 
582         return FALSE;
583 }
584 
585 
586 static void
set_playing_state(GthMediaViewerPage * self,gboolean playing)587 set_playing_state (GthMediaViewerPage *self,
588 	           gboolean            playing)
589 {
590 	self->priv->playing = playing;
591 	if (self->priv->playing)
592 		gth_screensaver_inhibit (self->priv->screensaver,
593 					 GTK_WINDOW (self->priv->browser),
594 					 _("Playing video"));
595 	else
596 		gth_screensaver_uninhibit (self->priv->screensaver);
597 }
598 
599 
600 static void
update_play_button(GthMediaViewerPage * self,GstState new_state)601 update_play_button (GthMediaViewerPage *self,
602 		    GstState            new_state)
603 {
604 	if (! self->priv->playing && (new_state == GST_STATE_PLAYING)) {
605 		set_playing_state (self, TRUE);
606 		gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_button_image")), "media-playback-pause-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
607 		gtk_widget_set_tooltip_text (GET_WIDGET ("play_button_image"), _("Pause"));
608 
609 		if (self->priv->update_progress_id == 0)
610 			self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
611 
612 		update_playback_info (self);
613 	}
614 	else if (self->priv->playing && (new_state != GST_STATE_PLAYING)) {
615 		GtkWidget *play_button = GET_WIDGET ("play_button_image");
616 
617 		set_playing_state (self, FALSE);
618 		gtk_image_set_from_icon_name (GTK_IMAGE (play_button),
619 					      "media-playback-start-symbolic",
620 					      GTK_ICON_SIZE_LARGE_TOOLBAR);
621 		gtk_widget_set_tooltip_text (GET_WIDGET ("play_button_image"), _("Play"));
622 
623 		if (self->priv->update_progress_id != 0) {
624 			 g_source_remove (self->priv->update_progress_id);
625 			 self->priv->update_progress_id = 0;
626 		}
627 
628 		update_playback_info (self);
629 	}
630 
631 	gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
632 }
633 
634 
635 /*
636 static char *
637 state_description (GstState state)
638 {
639 	switch (state) {
640 	case GST_STATE_VOID_PENDING:
641 		return "void pending";
642 	case GST_STATE_NULL:
643 		return "null";
644 	case GST_STATE_READY:
645 		return "ready";
646 	case GST_STATE_PAUSED:
647 		return "paused";
648 	case GST_STATE_PLAYING:
649 		return "playing";
650 	}
651 	return "error";
652 }
653 */
654 
655 
656 static void
reset_player_state(GthMediaViewerPage * self)657 reset_player_state (GthMediaViewerPage *self)
658 {
659         if (self->priv->update_progress_id != 0) {
660                 g_source_remove (self->priv->update_progress_id);
661                 self->priv->update_progress_id = 0;
662         }
663 
664 	update_play_button (self, GST_STATE_NULL);
665 	self->priv->rate = 1.0;
666 	set_playing_state (self, FALSE);
667 }
668 
669 
670 static void
update_stream_info(GthMediaViewerPage * self)671 update_stream_info (GthMediaViewerPage *self)
672 {
673 	GstElement *audio_sink;
674 	GstElement *video_sink;
675 	GstPad     *audio_pad;
676 	GstPad     *video_pad;
677 
678 	g_object_get (self->priv->playbin,
679 		      "audio-sink", &audio_sink,
680 		      "video-sink", &video_sink,
681 		      NULL);
682 
683 	self->priv->has_audio = FALSE;
684 	self->priv->has_video = FALSE;
685 
686 	if (audio_sink != NULL) {
687 		audio_pad = gst_element_get_static_pad (GST_ELEMENT (audio_sink), "sink");
688 		if (audio_pad != NULL) {
689 			GstCaps *caps;
690 
691 			if ((caps = gst_pad_get_current_caps (audio_pad)) != NULL) {
692 				self->priv->has_audio = TRUE;
693 				gst_caps_unref (caps);
694 			}
695 		}
696 	}
697 
698 	if (video_sink != NULL) {
699 		video_pad = gst_element_get_static_pad (GST_ELEMENT (video_sink), "sink");
700 		if (video_pad != NULL) {
701 			GstCaps *caps;
702 
703 			if ((caps = gst_pad_get_current_caps (video_pad)) != NULL) {
704 				GstStructure *structure;
705 				int           video_width;
706 				int           video_height;
707 
708 				structure = gst_caps_get_structure (caps, 0);
709 				gst_structure_get_fraction (structure, "framerate", &self->priv->video_fps_n, &self->priv->video_fps_d);
710 				if (gst_structure_get_int (structure, "width", &video_width)
711 				    && gst_structure_get_int (structure, "height", &video_height))
712 				{
713 					g_file_info_set_attribute_int32 (self->priv->updated_info, "frame::width", video_width);
714 					g_file_info_set_attribute_int32 (self->priv->updated_info, "frame::height", video_height);
715 					self->priv->has_video = TRUE;
716 					self->priv->video_width = video_width;
717 					self->priv->video_height = video_height;
718 				}
719 
720 				gst_caps_unref (caps);
721 			}
722 		}
723 	}
724 
725 	gtk_stack_set_visible_child_name (GTK_STACK (self->priv->area_box), self->priv->has_video ? "video-area" : "audio-area");
726 	update_zoom_info (self);
727 }
728 
729 
730 static void
bus_message_cb(GstBus * bus,GstMessage * message,gpointer user_data)731 bus_message_cb (GstBus     *bus,
732                 GstMessage *message,
733                 gpointer    user_data)
734 {
735 	GthMediaViewerPage *self = user_data;
736 
737 	if (GST_MESSAGE_SRC (message) != GST_OBJECT (self->priv->playbin))
738 		return;
739 
740 	switch (GST_MESSAGE_TYPE (message)) {
741 	case GST_MESSAGE_STATE_CHANGED: {
742 		GstState old_state;
743 		GstState new_state;
744 		GstState pending_state;
745 
746 		old_state = new_state = GST_STATE_NULL;
747 		gst_message_parse_state_changed (message, &old_state, &new_state, &pending_state);
748 		if (old_state == new_state)
749 			break;
750 
751 		/*
752 		g_print ("old state: %s\n", state_description (old_state));
753 		g_print ("new state: %s\n", state_description (new_state));
754 		g_print ("pending state: %s\n", state_description (pending_state));
755 		g_print ("\n");
756 		*/
757 
758 		self->priv->paused = (new_state == GST_STATE_PAUSED);
759 		update_current_position_bar (self);
760 
761 		if ((old_state == GST_STATE_NULL) && (new_state == GST_STATE_READY) && (pending_state != GST_STATE_PAUSED)) {
762 			update_stream_info (self);
763 			gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
764 			gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, self->priv->updated_info, TRUE);
765 		}
766 		if ((old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED)) {
767 			update_stream_info (self);
768 			gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
769 			gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, self->priv->updated_info, TRUE);
770 		}
771 		if ((old_state == GST_STATE_READY) || (new_state == GST_STATE_PAUSED))
772 			update_volume_from_playbin (self);
773 		if ((old_state == GST_STATE_PLAYING) || (new_state == GST_STATE_PLAYING))
774 			update_play_button (self, new_state);
775 		break;
776 	}
777 
778 	case GST_MESSAGE_DURATION_CHANGED:
779 		self->priv->duration = 0;
780 		update_current_position_bar (self);
781 		break;
782 
783 	case GST_MESSAGE_EOS:
784 		if (self->priv->loop && self->priv->playing)
785 			gst_element_seek (self->priv->playbin,
786 					  self->priv->rate,
787 					  GST_FORMAT_TIME,
788 					  GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
789 					  GST_SEEK_TYPE_SET,
790 					  0.0,
791 					  GST_SEEK_TYPE_NONE,
792 					  0.0);
793 		else
794 			reset_player_state (self);
795 		break;
796 
797 	case GST_MESSAGE_BUFFERING: {
798 		int percent = 0;
799 		gst_message_parse_buffering (message, &percent);
800 		gst_element_set_state (self->priv->playbin, (percent == 100) ? GST_STATE_PLAYING : GST_STATE_PAUSED);
801 		break;
802 	}
803 
804 	case GST_MESSAGE_ERROR:
805 		gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, NULL, FALSE);
806 		break;
807 
808 	default:
809 		break;
810 	}
811 }
812 
813 
814 static void
playbin_notify_volume_cb(GObject * playbin,GParamSpec * pspec,gpointer user_data)815 playbin_notify_volume_cb (GObject    *playbin,
816 			  GParamSpec *pspec,
817 			  gpointer    user_data)
818 {
819 	GthMediaViewerPage *self = user_data;
820 
821 	if (self->priv->update_volume_id == 0)
822 		self->priv->update_volume_id = g_idle_add ((GSourceFunc) update_volume_from_playbin, user_data);
823 }
824 
825 
826 static void
create_playbin(GthMediaViewerPage * self)827 create_playbin (GthMediaViewerPage *self)
828 {
829 	GstElement *video_sink;
830 	GstBus     *bus;
831 
832 	if (self->priv->playbin != NULL)
833 		return;
834 
835 	self->priv->playbin = gst_element_factory_make ("playbin", "playbin");
836 
837 	if (g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_USE_HARDWARE_ACCEL)) {
838 		GstElement *video_bin;
839 		GstElement *glupload;
840 		GstPad     *pad;
841 		GstPad     *ghost_pad;
842 
843 		/* pipeline taken from GNOME Twitch */
844 
845 		video_sink = gst_element_factory_make ("gtkglsink", "sink");
846 		video_bin = gst_bin_new ("video_bin");
847 		glupload = gst_element_factory_make ("glupload", NULL);
848 		gst_bin_add_many (GST_BIN(video_bin), glupload, video_sink, NULL);
849 		gst_element_link_many (glupload, video_sink, NULL);
850 		pad = gst_element_get_static_pad (glupload, "sink");
851 		ghost_pad = gst_ghost_pad_new ("sink", pad);
852 		gst_pad_set_active (ghost_pad, TRUE);
853 		gst_element_add_pad (video_bin, ghost_pad);
854 		g_object_set (self->priv->playbin, "video-sink", video_bin, NULL);
855 	}
856 	else {
857 		video_sink = gst_element_factory_make ("gtksink", "sink");
858 		g_object_set (self->priv->playbin, "video-sink", video_sink, NULL);
859 	}
860 
861 	g_object_get (video_sink, "widget", &self->priv->video_area, NULL);
862 	gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->video_area), "video-player");
863 	gtk_widget_add_events (self->priv->video_area,
864 			       (gtk_widget_get_events (self->priv->video_area)
865 				| GDK_EXPOSURE_MASK
866 				| GDK_BUTTON_PRESS_MASK
867 				| GDK_BUTTON_RELEASE_MASK
868 				| GDK_POINTER_MOTION_MASK
869 				| GDK_POINTER_MOTION_HINT_MASK
870 				| GDK_BUTTON_MOTION_MASK
871 				| GDK_SCROLL_MASK));
872 	gtk_widget_set_can_focus (self->priv->video_area, TRUE);
873 	gtk_widget_show (self->priv->video_area);
874 
875 	g_signal_connect (G_OBJECT (self->priv->video_area),
876 			  "realize",
877 			  G_CALLBACK (video_area_realize_cb),
878 			  self);
879 	g_signal_connect (G_OBJECT (self->priv->video_area),
880 			  "unrealize",
881 			  G_CALLBACK (video_area_unrealize_cb),
882 			  self);
883 	g_signal_connect (G_OBJECT (self->priv->video_area),
884 			  "size-allocate",
885 			  G_CALLBACK (video_area_size_allocate_cb),
886 			  self);
887 
888 	gtk_stack_add_named (GTK_STACK (self->priv->area_box), self->priv->video_area, "video-area");
889 	gtk_widget_show (self->priv->video_area);
890 
891 	g_object_set (self->priv->playbin,
892 		      "volume", (double) g_settings_get_int (self->priv->settings, PREF_GSTREAMER_TOOLS_VOLUME) / 100.0,
893 		      "force-aspect-ratio", TRUE,
894 		      NULL);
895 
896 	bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin));
897 	gst_bus_add_signal_watch (bus);
898 
899 	g_signal_connect (self->priv->playbin,
900 			  "notify::volume",
901 			  G_CALLBACK (playbin_notify_volume_cb),
902 			  self);
903 	g_signal_connect (bus,
904 			  "message",
905 			  G_CALLBACK (bus_message_cb),
906 			  self);
907 }
908 
909 
910 static void
gth_media_viewer_page_real_activate(GthViewerPage * base,GthBrowser * browser)911 gth_media_viewer_page_real_activate (GthViewerPage *base,
912 				     GthBrowser    *browser)
913 {
914 	GthMediaViewerPage *self;
915 
916 	if (! gstreamer_init ())
917 		return;
918 
919 	self = (GthMediaViewerPage*) base;
920 
921 	self->priv->browser = browser;
922 	g_action_map_add_action_entries (G_ACTION_MAP (browser),
923 					 actions,
924 					 G_N_ELEMENTS (actions),
925 					 browser);
926 	self->priv->screenshot_button =
927 			gth_browser_add_header_bar_button (browser,
928 							   GTH_BROWSER_HEADER_SECTION_VIEWER_VIEW,
929 							   "camera-photo-symbolic",
930 							   _("Take a screenshot"),
931 							   "win.video-screenshot",
932 							   NULL);
933 	self->priv->fit_button =
934 			gth_browser_add_header_bar_toggle_button (browser,
935 								  GTH_BROWSER_HEADER_SECTION_VIEWER_ZOOM,
936 								  "view-zoom-fit-symbolic",
937 								  _("Fit to window"),
938 								  "win.video-zoom-fit",
939 								  NULL);
940 
941 	/* audio area */
942 
943 	self->priv->audio_area = gtk_drawing_area_new ();
944 	gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->audio_area), "video-player");
945 	gtk_widget_add_events (self->priv->audio_area, (gtk_widget_get_events (self->priv->audio_area)
946 						  | GDK_EXPOSURE_MASK
947 						  | GDK_BUTTON_PRESS_MASK
948 						  | GDK_BUTTON_RELEASE_MASK
949 						  | GDK_POINTER_MOTION_MASK
950 						  | GDK_POINTER_MOTION_HINT_MASK
951 						  | GDK_BUTTON_MOTION_MASK
952 						  | GDK_SCROLL_MASK));
953 	gtk_widget_set_can_focus (self->priv->audio_area, TRUE);
954 	gtk_widget_show (self->priv->audio_area);
955 
956 	g_signal_connect (G_OBJECT (self->priv->audio_area),
957 			  "draw",
958 			  G_CALLBACK (video_area_draw_cb),
959 			  self);
960 
961 	/* mediabar */
962 
963 	self->priv->builder = _gtk_builder_new_from_file ("mediabar.ui", "gstreamer_tools");
964 	self->priv->mediabar = GET_WIDGET ("mediabar");
965 	gtk_widget_set_halign (self->priv->mediabar, GTK_ALIGN_FILL);
966 	gtk_widget_set_valign (self->priv->mediabar, GTK_ALIGN_END);
967 
968 	gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_slower_image")),
969 				      "media-seek-backward-symbolic",
970 				      GTK_ICON_SIZE_MENU);
971 	gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_faster_image")),
972 				      "media-seek-forward-symbolic",
973 				      GTK_ICON_SIZE_MENU);
974 
975 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("loop_button")), self->priv->loop);
976 
977 	g_signal_connect (GET_WIDGET ("volume_adjustment"),
978 			  "value-changed",
979 			  G_CALLBACK (volume_value_changed_cb),
980 			  self);
981 	g_signal_connect (GET_WIDGET ("position_adjustment"),
982 			  "value-changed",
983 			  G_CALLBACK (position_value_changed_cb),
984 			  self);
985 	g_signal_connect (GET_WIDGET ("play_button"),
986 			  "clicked",
987 			  G_CALLBACK (play_button_clicked_cb),
988 			  self);
989 	g_signal_connect (GET_WIDGET ("play_slower_button"),
990 			  "clicked",
991 			  G_CALLBACK (play_slower_button_clicked_cb),
992 			  self);
993 	g_signal_connect (GET_WIDGET ("play_faster_button"),
994 			  "clicked",
995 			  G_CALLBACK (play_faster_button_clicked_cb),
996 			  self);
997 	g_signal_connect (GET_WIDGET ("loop_button"),
998 			  "clicked",
999 			  G_CALLBACK (loop_button_clicked_cb),
1000 			  self);
1001 
1002 	self->priv->mediabar_revealer = gtk_revealer_new ();
1003 	gtk_revealer_set_transition_type (GTK_REVEALER (self->priv->mediabar_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
1004 	gtk_widget_set_halign (self->priv->mediabar_revealer, GTK_ALIGN_FILL);
1005 	gtk_widget_set_valign (self->priv->mediabar_revealer, GTK_ALIGN_END);
1006 	gtk_widget_show (self->priv->mediabar_revealer);
1007 	gtk_container_add (GTK_CONTAINER (self->priv->mediabar_revealer), self->priv->mediabar);
1008 
1009 	self->priv->area_box = gtk_stack_new ();
1010 	gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->area_box), "video-player");
1011 	gtk_stack_add_named (GTK_STACK (self->priv->area_box), self->priv->audio_area, "audio-area");
1012 	gtk_widget_show (self->priv->area_box);
1013 
1014 	g_signal_connect (G_OBJECT (self->priv->area_box),
1015 			  "button_press_event",
1016 			  G_CALLBACK (video_area_button_press_cb),
1017 			  self);
1018 	g_signal_connect (G_OBJECT (self->priv->area_box),
1019 			  "popup-menu",
1020 			  G_CALLBACK (video_area_popup_menu_cb),
1021 			  self);
1022 	g_signal_connect (G_OBJECT (self->priv->area_box),
1023 			  "scroll_event",
1024 			  G_CALLBACK (video_area_scroll_event_cb),
1025 			  self);
1026 
1027 	self->priv->area_overlay = gtk_overlay_new ();
1028 	gtk_container_add (GTK_CONTAINER (self->priv->area_overlay), self->priv->area_box);
1029 	gtk_overlay_add_overlay (GTK_OVERLAY (self->priv->area_overlay), self->priv->mediabar_revealer);
1030 	gtk_widget_show (self->priv->area_overlay);
1031 
1032 	gth_browser_set_viewer_widget (browser, self->priv->area_overlay);
1033 
1034 	gtk_widget_realize (self->priv->audio_area);
1035 	gth_browser_register_viewer_control (self->priv->browser, self->priv->mediabar_revealer);
1036 	gth_browser_register_viewer_control (self->priv->browser, gtk_scale_button_get_popup (GTK_SCALE_BUTTON (GET_WIDGET ("volumebutton"))));
1037 
1038 	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
1039 
1040 	create_playbin (self);
1041 
1042 	gth_media_viewer_page_set_fit_if_larger (self, g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT));
1043 }
1044 
1045 
1046 static void
wait_playbin_state_change_to_complete(GthMediaViewerPage * self)1047 wait_playbin_state_change_to_complete (GthMediaViewerPage *self)
1048 {
1049 	(void) gst_element_get_state (self->priv->playbin,
1050 				      NULL,
1051 				      NULL,
1052 				      GST_SECOND * 10);
1053 }
1054 
1055 
1056 static void
gth_media_viewer_page_real_deactivate(GthViewerPage * base)1057 gth_media_viewer_page_real_deactivate (GthViewerPage *base)
1058 {
1059 	GthMediaViewerPage *self;
1060 
1061 	self = (GthMediaViewerPage*) base;
1062 
1063 	gth_browser_unregister_viewer_control (self->priv->browser, gtk_scale_button_get_popup (GTK_SCALE_BUTTON (GET_WIDGET ("volumebutton"))));
1064 	gth_browser_unregister_viewer_control (self->priv->browser, self->priv->mediabar_revealer);
1065 
1066 	if (self->priv->builder != NULL) {
1067 		g_object_unref (self->priv->builder);
1068 		self->priv->builder = NULL;
1069 	}
1070 
1071         if (self->priv->update_progress_id != 0) {
1072                 g_source_remove (self->priv->update_progress_id);
1073                 self->priv->update_progress_id = 0;
1074         }
1075 
1076         if (self->priv->update_volume_id != 0) {
1077                 g_source_remove (self->priv->update_volume_id);
1078                 self->priv->update_volume_id = 0;
1079         }
1080 
1081 	if (self->priv->playbin != NULL) {
1082 		double volume;
1083 
1084 		g_object_get (self->priv->playbin, "volume", &volume, NULL);
1085 		g_settings_set_int (self->priv->settings, PREF_GSTREAMER_TOOLS_VOLUME, (int) (volume * 100.0));
1086 
1087 		g_settings_set_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT, self->priv->fit_if_larger);
1088 
1089 		_g_signal_handlers_disconnect_by_data (self->priv->playbin, self);
1090 		_g_signal_handlers_disconnect_by_data (self->priv->video_area, self);
1091 
1092 		gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
1093 		wait_playbin_state_change_to_complete (self);
1094 		gst_object_unref (GST_OBJECT (self->priv->playbin));
1095 		self->priv->playbin = NULL;
1096 		self->priv->video_area = NULL;
1097 		self->priv->audio_area = NULL;
1098 	}
1099 
1100 	gtk_widget_destroy (self->priv->screenshot_button);
1101 	gtk_widget_destroy (self->priv->fit_button);
1102 	self->priv->screenshot_button = NULL;
1103 	self->priv->fit_button = NULL;
1104 
1105 	gth_browser_set_viewer_widget (self->priv->browser, NULL);
1106 }
1107 
1108 
1109 static void
_gth_media_viewer_page_set_uri(GthMediaViewerPage * self,const char * uri,GstState state)1110 _gth_media_viewer_page_set_uri (GthMediaViewerPage *self,
1111 				const char         *uri,
1112 				GstState            state)
1113 {
1114 	g_return_if_fail (self->priv->playbin != NULL);
1115 
1116 	gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
1117 
1118 	g_object_set (G_OBJECT (self->priv->playbin), "uri", uri, NULL);
1119 	gst_element_set_state (self->priv->playbin, state);
1120 	wait_playbin_state_change_to_complete (self);
1121 }
1122 
1123 
1124 static void
gth_media_viewer_page_real_show(GthViewerPage * base)1125 gth_media_viewer_page_real_show (GthViewerPage *base)
1126 {
1127 	GthMediaViewerPage *self = GTH_MEDIA_VIEWER_PAGE (base);
1128 
1129 	self->priv->visible = TRUE;
1130 	self->priv->background_painted = FALSE;
1131 	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
1132 
1133 	if (self->priv->file_data != NULL) {
1134 		char *uri;
1135 
1136 		uri = g_file_get_uri (self->priv->file_data->file);
1137 		_gth_media_viewer_page_set_uri (self, uri, GST_STATE_PLAYING);
1138 
1139 		g_free (uri);
1140 	}
1141 }
1142 
1143 
1144 static void
gth_media_viewer_page_real_hide(GthViewerPage * base)1145 gth_media_viewer_page_real_hide (GthViewerPage *base)
1146 {
1147 	GthMediaViewerPage *self;
1148 
1149 	self = (GthMediaViewerPage*) base;
1150 
1151 	self->priv->visible = FALSE;
1152 	if ((self->priv->playbin != NULL) && self->priv->playing)
1153 		gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
1154 }
1155 
1156 
1157 static gboolean
gth_media_viewer_page_real_can_view(GthViewerPage * base,GthFileData * file_data)1158 gth_media_viewer_page_real_can_view (GthViewerPage *base,
1159 				     GthFileData   *file_data)
1160 {
1161 	g_return_val_if_fail (file_data != NULL, FALSE);
1162 
1163 	return _g_mime_type_is_video (gth_file_data_get_mime_type (file_data)) || _g_mime_type_is_audio (gth_file_data_get_mime_type (file_data));
1164 }
1165 
1166 
1167 static void
gth_media_viewer_page_real_view(GthViewerPage * base,GthFileData * file_data)1168 gth_media_viewer_page_real_view (GthViewerPage *base,
1169 				 GthFileData   *file_data)
1170 {
1171 	GthMediaViewerPage *self;
1172 	char               *uri;
1173 
1174 	self = (GthMediaViewerPage*) base;
1175 	g_return_if_fail (file_data != NULL);
1176 	g_return_if_fail (self->priv->playbin != NULL);
1177 
1178 	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
1179 
1180 	if ((self->priv->file_data != NULL)
1181 	    && g_file_equal (file_data->file, self->priv->file_data->file)
1182 	    && (gth_file_data_get_mtime (file_data) == gth_file_data_get_mtime (self->priv->file_data)))
1183 	{
1184 		return;
1185 	}
1186 
1187 	/**/
1188 
1189 	_g_object_unref (self->priv->file_data);
1190 	_g_object_unref (self->priv->updated_info);
1191 	self->priv->file_data = gth_file_data_dup (file_data);
1192 	self->priv->updated_info = g_file_info_new ();
1193 
1194 	self->priv->duration = 0;
1195 	self->priv->has_audio = FALSE;
1196 	self->priv->has_video = FALSE;
1197 	self->priv->background_painted = FALSE;
1198 
1199 	_g_object_unref (self->priv->icon);
1200 	self->priv->icon = NULL;
1201 
1202 	_gth_media_viewer_page_update_caption (self);
1203 
1204 	/**/
1205 
1206 	g_signal_handlers_block_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
1207 	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment")), 0.0);
1208 	g_signal_handlers_unblock_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
1209 	reset_player_state (self);
1210 
1211 	uri = g_file_get_uri (self->priv->file_data->file);
1212 	_gth_media_viewer_page_set_uri (self, uri, self->priv->visible ? GST_STATE_PLAYING : GST_STATE_PAUSED);
1213 
1214 	g_free (uri);
1215 }
1216 
1217 
1218 static void
gth_media_viewer_page_real_focus(GthViewerPage * base)1219 gth_media_viewer_page_real_focus (GthViewerPage *base)
1220 {
1221 	GthMediaViewerPage *self = (GthMediaViewerPage*) base;
1222 	GtkWidget          *widget;
1223 
1224 	widget = NULL;
1225 	if (self->priv->has_video)
1226 		widget = self->priv->video_area;
1227 	else if (self->priv->has_audio)
1228 		widget = self->priv->audio_area;
1229 
1230 	if ((widget != NULL) && gtk_widget_get_realized (widget) && gtk_widget_get_mapped (widget))
1231 		gtk_widget_grab_focus (widget);
1232 }
1233 
1234 
1235 static void
gth_media_viewer_page_real_fullscreen(GthViewerPage * base,gboolean active)1236 gth_media_viewer_page_real_fullscreen (GthViewerPage *base,
1237 				       gboolean       active)
1238 {
1239 	/* void */
1240 }
1241 
1242 
1243 static void
gth_media_viewer_page_real_show_pointer(GthViewerPage * base,gboolean show)1244 gth_media_viewer_page_real_show_pointer (GthViewerPage *base,
1245 				         gboolean       show)
1246 {
1247 	GthMediaViewerPage *self = (GthMediaViewerPage*) base;
1248 
1249 	if (show == self->priv->cursor_visible)
1250 		return;
1251 
1252 	self->priv->cursor_visible = show;
1253 
1254 	if (self->priv->video_area != NULL) {
1255 		if (show && (self->priv->cursor != NULL))
1256 			gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor);
1257 
1258 		if (! show && gth_browser_get_is_fullscreen (self->priv->browser) && (self->priv->cursor_void != NULL))
1259 			gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor_void);
1260 	}
1261 
1262 	gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->mediabar_revealer), show);
1263 }
1264 
1265 
1266 static void
gth_media_viewer_page_real_update_sensitivity(GthViewerPage * base)1267 gth_media_viewer_page_real_update_sensitivity (GthViewerPage *base)
1268 {
1269 	GthMediaViewerPage *self = (GthMediaViewerPage *) base;
1270 
1271 	gtk_widget_set_sensitive (GET_WIDGET ("volume_box"), self->priv->has_audio);
1272 	gtk_widget_set_sensitive (GET_WIDGET ("play_button"), self->priv->has_video || self->priv->has_audio);
1273 	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "video-screenshot", self->priv->has_video);
1274 	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "video-zoom-fit", self->priv->has_video);
1275 }
1276 
1277 
1278 static gboolean
gth_media_viewer_page_real_can_save(GthViewerPage * base)1279 gth_media_viewer_page_real_can_save (GthViewerPage *base)
1280 {
1281 	return FALSE;
1282 }
1283 
1284 
1285 static void
gth_media_viewer_page_real_save(GthViewerPage * base,GFile * file,FileSavedFunc func,gpointer user_data)1286 gth_media_viewer_page_real_save (GthViewerPage *base,
1287 				 GFile         *file,
1288 				 FileSavedFunc  func,
1289 				 gpointer       user_data)
1290 {
1291 	/* void */
1292 }
1293 
1294 
1295 static void
gth_media_viewer_page_real_save_as(GthViewerPage * base,FileSavedFunc func,gpointer user_data)1296 gth_media_viewer_page_real_save_as (GthViewerPage *base,
1297 				    FileSavedFunc  func,
1298 				    gpointer       user_data)
1299 {
1300 	/* void */
1301 }
1302 
1303 
1304 static void
gth_media_viewer_page_real_revert(GthViewerPage * base)1305 gth_media_viewer_page_real_revert (GthViewerPage *base)
1306 {
1307 	/* void */
1308 }
1309 
1310 
1311 static void
gth_media_viewer_page_real_update_info(GthViewerPage * base,GthFileData * file_data)1312 gth_media_viewer_page_real_update_info (GthViewerPage *base,
1313 				        GthFileData   *file_data)
1314 {
1315 	GthMediaViewerPage *self = GTH_MEDIA_VIEWER_PAGE (base);
1316 
1317 	if (! _g_file_equal (self->priv->file_data->file, file_data->file))
1318 		return;
1319 
1320 	_g_object_unref (self->priv->file_data);
1321 	self->priv->file_data = gth_file_data_dup (file_data);
1322 }
1323 
1324 
1325 static void
gth_media_viewer_page_finalize(GObject * obj)1326 gth_media_viewer_page_finalize (GObject *obj)
1327 {
1328 	GthMediaViewerPage *self;
1329 
1330 	self = GTH_MEDIA_VIEWER_PAGE (obj);
1331 
1332         if (self->priv->update_progress_id != 0) {
1333                 g_source_remove (self->priv->update_progress_id);
1334                 self->priv->update_progress_id = 0;
1335         }
1336 
1337         if (self->priv->update_volume_id != 0) {
1338                 g_source_remove (self->priv->update_volume_id);
1339                 self->priv->update_volume_id = 0;
1340         }
1341 
1342         _g_object_unref (self->priv->icon);
1343 	_g_object_unref (self->priv->file_data);
1344 	_g_object_unref (self->priv->updated_info);
1345 	if (self->priv->screensaver != NULL) {
1346 		gth_screensaver_uninhibit (self->priv->screensaver);
1347 		g_object_unref (self->priv->screensaver);
1348 	}
1349 	_g_object_unref (self->priv->settings);
1350 
1351 	G_OBJECT_CLASS (gth_media_viewer_page_parent_class)->finalize (obj);
1352 }
1353 
1354 
1355 static void
gth_media_viewer_page_class_init(GthMediaViewerPageClass * klass)1356 gth_media_viewer_page_class_init (GthMediaViewerPageClass *klass)
1357 {
1358 	G_OBJECT_CLASS (klass)->finalize = gth_media_viewer_page_finalize;
1359 }
1360 
1361 
1362 static void
gth_viewer_page_interface_init(GthViewerPageInterface * iface)1363 gth_viewer_page_interface_init (GthViewerPageInterface *iface)
1364 {
1365 	iface->activate = gth_media_viewer_page_real_activate;
1366 	iface->deactivate = gth_media_viewer_page_real_deactivate;
1367 	iface->show = gth_media_viewer_page_real_show;
1368 	iface->hide = gth_media_viewer_page_real_hide;
1369 	iface->can_view = gth_media_viewer_page_real_can_view;
1370 	iface->view = gth_media_viewer_page_real_view;
1371 	iface->focus = gth_media_viewer_page_real_focus;
1372 	iface->fullscreen = gth_media_viewer_page_real_fullscreen;
1373 	iface->show_pointer = gth_media_viewer_page_real_show_pointer;
1374 	iface->update_sensitivity = gth_media_viewer_page_real_update_sensitivity;
1375 	iface->can_save = gth_media_viewer_page_real_can_save;
1376 	iface->save = gth_media_viewer_page_real_save;
1377 	iface->save_as = gth_media_viewer_page_real_save_as;
1378 	iface->revert = gth_media_viewer_page_real_revert;
1379 	iface->update_info = gth_media_viewer_page_real_update_info;
1380 }
1381 
1382 
1383 static void
pref_zoom_to_fit_changed(GSettings * settings,char * key,gpointer user_data)1384 pref_zoom_to_fit_changed (GSettings *settings,
1385 			  char      *key,
1386 			  gpointer   user_data)
1387 {
1388 	GthMediaViewerPage *self = user_data;
1389 	gth_media_viewer_page_set_fit_if_larger (self, g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT));
1390 }
1391 
1392 
1393 static void
gth_media_viewer_page_init(GthMediaViewerPage * self)1394 gth_media_viewer_page_init (GthMediaViewerPage *self)
1395 {
1396 	self->priv = gth_media_viewer_page_get_instance_private (self);
1397 	self->priv->settings = g_settings_new (GTHUMB_GSTREAMER_TOOLS_SCHEMA);
1398 	self->priv->update_progress_id = 0;
1399 	self->priv->update_volume_id = 0;
1400 	self->priv->has_video = FALSE;
1401 	self->priv->has_audio = FALSE;
1402 	self->priv->video_fps_n = 0;
1403 	self->priv->video_fps_d = 0;
1404 	self->priv->icon = NULL;
1405 	self->priv->cursor_visible = TRUE;
1406 	self->priv->screensaver = gth_screensaver_new (NULL);
1407 	self->priv->visible = FALSE;
1408 	self->priv->screenshot_button = NULL;
1409 	self->priv->background_painted = FALSE;
1410 	self->priv->file_data = NULL;
1411 	self->priv->updated_info = NULL;
1412 	self->priv->loop = FALSE;
1413 	self->priv->fit_if_larger = TRUE;
1414 
1415 	/* settings notifications */
1416 
1417 	g_signal_connect (self->priv->settings,
1418 			  "changed::" PREF_GSTREAMER_ZOOM_TO_FIT,
1419 			  G_CALLBACK (pref_zoom_to_fit_changed),
1420 			  self);
1421 }
1422 
1423 
1424 GthBrowser *
gth_media_viewer_page_get_browser(GthMediaViewerPage * self)1425 gth_media_viewer_page_get_browser (GthMediaViewerPage *self)
1426 {
1427 	return self->priv->browser;
1428 }
1429 
1430 
1431 GstElement *
gth_media_viewer_page_get_playbin(GthMediaViewerPage * self)1432 gth_media_viewer_page_get_playbin (GthMediaViewerPage *self)
1433 {
1434 	return self->priv->playbin;
1435 }
1436 
1437 
1438 gboolean
gth_media_viewer_page_is_playing(GthMediaViewerPage * self)1439 gth_media_viewer_page_is_playing (GthMediaViewerPage *self)
1440 {
1441 	return self->priv->playing;
1442 }
1443 
1444 
1445 void
gth_media_viewer_page_get_video_fps(GthMediaViewerPage * self,int * video_fps_n,int * video_fps_d)1446 gth_media_viewer_page_get_video_fps (GthMediaViewerPage *self,
1447 				     int                *video_fps_n,
1448 				     int                *video_fps_d)
1449 {
1450 	if (video_fps_n != NULL)
1451 		*video_fps_n = self->priv->video_fps_n;
1452 	if (video_fps_d != NULL)
1453 		*video_fps_d = self->priv->video_fps_d;
1454 }
1455 
1456 
1457 GthFileData *
gth_media_viewer_page_get_file_data(GthMediaViewerPage * self)1458 gth_media_viewer_page_get_file_data (GthMediaViewerPage *self)
1459 {
1460 	return self->priv->file_data;
1461 }
1462 
1463 
1464 void
gth_media_viewer_page_toggle_play(GthMediaViewerPage * self)1465 gth_media_viewer_page_toggle_play (GthMediaViewerPage *self)
1466 {
1467 	if (self->priv->playbin == NULL)
1468 		return;
1469 
1470 	if (! self->priv->playing) {
1471 		if (! self->priv->paused) {
1472 			gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
1473 			gst_element_seek (self->priv->playbin,
1474 					  self->priv->rate,
1475 					  GST_FORMAT_TIME,
1476 					  GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
1477 					  GST_SEEK_TYPE_SET,
1478 					  0.0,
1479 					  GST_SEEK_TYPE_NONE,
1480 					  0.0);
1481 		}
1482 		else {
1483 			gint64 current_value;
1484 
1485 			current_value = (gint64) (gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment"))) / 100.0 * self->priv->duration);
1486 			gst_element_seek (self->priv->playbin,
1487 					  self->priv->rate,
1488 					  GST_FORMAT_TIME,
1489 					  GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
1490 					  GST_SEEK_TYPE_SET,
1491 					  current_value,
1492 					  GST_SEEK_TYPE_NONE,
1493 					  0.0);
1494 		}
1495 		gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING);
1496 	}
1497 	else
1498 		gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
1499 }
1500 
1501 
1502 void
gth_media_viewer_page_set_fit_if_larger(GthMediaViewerPage * self,gboolean fit_if_larger)1503 gth_media_viewer_page_set_fit_if_larger (GthMediaViewerPage *self,
1504 					 gboolean            fit_if_larger)
1505 {
1506 	GtkAlign alignment;
1507 
1508 	self->priv->fit_if_larger = fit_if_larger;
1509 	if (self->priv->video_area != NULL) {
1510 		alignment = self->priv->fit_if_larger ? GTK_ALIGN_FILL : GTK_ALIGN_CENTER;
1511 		gtk_widget_set_valign (self->priv->video_area, alignment);
1512 		gtk_widget_set_halign (self->priv->video_area, alignment);
1513 
1514 		gth_window_change_action_state (GTH_WINDOW (self->priv->browser), "video-zoom-fit", self->priv->fit_if_larger);
1515 	}
1516 }
1517