/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "actions.h"
#include "gth-media-viewer-page.h"
#include "preferences.h"
#include "shortcuts.h"
#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
#define PROGRESS_DELAY 500
static void gth_viewer_page_interface_init (GthViewerPageInterface *iface);
struct _GthMediaViewerPagePrivate {
GthBrowser *browser;
GSettings *settings;
GthFileData *file_data;
GFileInfo *updated_info;
GstElement *playbin;
GtkBuilder *builder;
GtkWidget *video_area;
GtkWidget *audio_area;
GtkWidget *area_box;
GtkWidget *area_overlay;
gboolean fit_if_larger;
gboolean visible;
gboolean playing;
gboolean paused;
gboolean loop;
gint64 duration;
int video_fps_n;
int video_fps_d;
int video_width;
int video_height;
gboolean has_video;
gboolean has_audio;
gulong update_progress_id;
gulong update_volume_id;
gdouble rate;
GtkWidget *mediabar;
GtkWidget *mediabar_revealer;
GdkPixbuf *icon;
PangoLayout *caption_layout;
GdkCursor *cursor;
GdkCursor *cursor_void;
gboolean cursor_visible;
GthScreensaver *screensaver;
GtkWidget *screenshot_button;
GtkWidget *fit_button;
gboolean background_painted;
};
G_DEFINE_TYPE_WITH_CODE (GthMediaViewerPage,
gth_media_viewer_page,
G_TYPE_OBJECT,
G_ADD_PRIVATE (GthMediaViewerPage)
G_IMPLEMENT_INTERFACE (GTH_TYPE_VIEWER_PAGE,
gth_viewer_page_interface_init))
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 };
static const GActionEntry actions[] = {
{ "video-screenshot", gth_browser_activate_video_screenshot },
{ "toggle-play", gth_browser_activate_toggle_play },
{ "toggle-mute", gth_browser_activate_toggle_mute },
{ "play-faster", gth_browser_activate_play_faster },
{ "play-slower", gth_browser_activate_play_slower },
{ "next-frame", gth_browser_activate_next_video_frame },
{ "skip-forward-smallest", gth_browser_activate_skip_forward_smallest },
{ "skip-forward-smaller", gth_browser_activate_skip_forward_smaller },
{ "skip-forward-small", gth_browser_activate_skip_forward_small },
{ "skip-forward-big", gth_browser_activate_skip_forward_big },
{ "skip-forward-bigger", gth_browser_activate_skip_forward_bigger },
{ "skip-back-smallest", gth_browser_activate_skip_back_smallest },
{ "skip-back-smaller", gth_browser_activate_skip_back_smaller },
{ "skip-back-small", gth_browser_activate_skip_back_small },
{ "skip-back-big", gth_browser_activate_skip_back_big },
{ "skip-back-bigger", gth_browser_activate_skip_back_bigger },
{ "video-zoom-fit", toggle_action_activated, NULL, "true", gth_browser_activate_video_zoom_fit },
};
static void
_gth_media_viewer_page_update_caption (GthMediaViewerPage *self)
{
if (self->priv->caption_layout == NULL)
return;
if (self->priv->file_data != NULL) {
GString *description;
GthMetadata *metadata;
description = g_string_new ("");
metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "general::title");
if (metadata != NULL) {
g_string_append (description, gth_metadata_get_formatted (metadata));
metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "audio-video::general::artist");
if (metadata != NULL) {
g_string_append (description, "\n");
g_string_append (description, gth_metadata_get_formatted (metadata));
}
}
else
g_string_append (description, g_file_info_get_display_name (self->priv->file_data->info));
pango_layout_set_text (self->priv->caption_layout, description->str, -1);
g_string_free (description, TRUE);
}
else
pango_layout_set_text (self->priv->caption_layout, "", -1);
gtk_widget_queue_draw (GTK_WIDGET (self->priv->audio_area));
}
static void
video_area_realize_cb (GtkWidget *widget,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
self->priv->cursor = _gdk_cursor_new_for_widget (widget, GDK_LEFT_PTR);
self->priv->cursor_void = _gdk_cursor_new_for_widget (self->priv->video_area, GDK_BLANK_CURSOR);
if (self->priv->cursor_visible)
gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor);
else
gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor_void);
self->priv->caption_layout = gtk_widget_create_pango_layout (widget, "");
pango_layout_set_alignment (self->priv->caption_layout, PANGO_ALIGN_CENTER);
_gth_media_viewer_page_update_caption (self);
self->priv->background_painted = FALSE;
}
static void
video_area_unrealize_cb (GtkWidget *widget,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
if (self->priv->cursor) {
g_object_unref (self->priv->cursor);
self->priv->cursor = NULL;
}
if (self->priv->cursor_void) {
g_object_unref (self->priv->cursor_void);
self->priv->cursor_void = NULL;
}
g_object_unref (self->priv->caption_layout);
self->priv->caption_layout = NULL;
}
static void
update_zoom_info (GthMediaViewerPage *self)
{
GtkAllocation allocation;
double view_width;
double view_height;
int zoom;
char *text;
if (! self->priv->has_video) {
gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), "");
return;
}
gtk_widget_get_allocation (self->priv->video_area, &allocation);
view_width = allocation.width;
view_height = (((double) self->priv->video_height / self->priv->video_width) * view_width);
if (view_height > allocation.height) {
view_height = allocation.height;
view_width = (((double) self->priv->video_width / self->priv->video_height) * view_height);
}
if (self->priv->video_width > 0)
zoom = (int) round ((double) view_width / self->priv->video_width * 100);
else if (self->priv->video_height > 0)
zoom = (int) round ((double) view_height / self->priv->video_height * 100);
else
zoom = 100;
text = g_strdup_printf (" %d%% ", zoom);
gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), text);
g_free (text);
}
static void
video_area_size_allocate_cb (GtkWidget *widget,
GdkRectangle *allocation,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
update_zoom_info (self);
}
static gboolean
video_area_draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
GtkAllocation allocation;
GtkStyleContext *style_context;
if (self->priv->has_video && self->priv->background_painted)
return FALSE;
gtk_widget_get_allocation (widget, &allocation);
style_context = gtk_widget_get_style_context (widget);
if (self->priv->icon == NULL) {
char *type;
GIcon *icon;
int size;
type = NULL;
if (self->priv->file_data != NULL)
type = g_content_type_from_mime_type (gth_file_data_get_mime_type (self->priv->file_data));
if (type == NULL)
type = g_content_type_from_mime_type ("text/plain");
icon = g_content_type_get_icon (type);
size = allocation.width;
if (size > allocation.height)
size = allocation.height;
size = size / 3;
self->priv->icon = _g_icon_get_pixbuf (icon, size, _gtk_widget_get_icon_theme (widget));
g_object_unref (icon);
g_free (type);
}
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
cairo_fill (cr);
if (self->priv->icon != NULL) {
int icon_w, icon_h;
int text_w;
int icon_x, icon_y;
PangoRectangle logical_rect;
int x, y;
PangoFontDescription *font;
icon_w = gdk_pixbuf_get_width (self->priv->icon);
icon_h = gdk_pixbuf_get_height (self->priv->icon);
text_w = (icon_w * 3 / 2);
pango_layout_set_width (self->priv->caption_layout, PANGO_SCALE * text_w);
pango_layout_get_extents (self->priv->caption_layout, NULL, &logical_rect);
icon_x = (allocation.width - icon_w) / 2;
x = (allocation.width - text_w) / 2;
icon_y = (allocation.height - (icon_h + PANGO_PIXELS (logical_rect.height))) / 2;
y = icon_y + icon_h;
gdk_cairo_set_source_pixbuf (cr, self->priv->icon, icon_x, icon_y);
cairo_rectangle (cr, icon_x, icon_y, icon_w, icon_h);
cairo_fill (cr);
cairo_move_to (cr, x, y);
gtk_style_context_get (style_context, gtk_widget_get_state_flags (widget), "font", &font, NULL);
pango_layout_set_font_description (self->priv->caption_layout, font);
pango_cairo_layout_path (cr, self->priv->caption_layout);
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_fill (cr);
}
self->priv->background_painted = TRUE;
return TRUE;
}
static gboolean
video_area_button_press_cb (GtkWidget *widget,
GdkEventButton *event,
GthMediaViewerPage *self)
{
if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1) ) {
gtk_button_clicked (GTK_BUTTON (GET_WIDGET ("play_button")));
return TRUE;
}
return gth_browser_viewer_button_press_cb (self->priv->browser, event);
}
static gboolean
video_area_popup_menu_cb (GtkWidget *widget,
GthMediaViewerPage *self)
{
gth_browser_file_menu_popup (self->priv->browser, NULL);
return TRUE;
}
static gboolean
video_area_scroll_event_cb (GtkWidget *widget,
GdkEventScroll *event,
GthMediaViewerPage *self)
{
return gth_browser_viewer_scroll_event_cb (self->priv->browser, event);
}
static void
volume_value_changed_cb (GtkAdjustment *adjustment,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
double v;
if (self->priv->playbin == NULL)
return;
/* cubic in [0,1], linear in [1,2] */
v = gtk_adjustment_get_value (adjustment) / 100.0;
if (v <= 1.0)
v = (v * v * v);
g_object_set (self->priv->playbin, "volume", v, NULL);
if (v > 0)
g_object_set (self->priv->playbin, "mute", FALSE, NULL);
}
static void position_value_changed_cb (GtkAdjustment *adjustment,
gpointer user_data);
static void
update_current_position_bar (GthMediaViewerPage *self)
{
gint64 current_value = 0;
if (gst_element_query_position (self->priv->playbin, GST_FORMAT_TIME, ¤t_value)) {
char *s;
if (self->priv->duration <= 0) {
gst_element_query_duration (self->priv->playbin, GST_FORMAT_TIME, &self->priv->duration);
s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (self->priv->duration));
gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_duration")), s);
g_free (s);
}
/*
g_print ("==> %" G_GINT64_FORMAT " / %" G_GINT64_FORMAT " (%0.3g)\n" ,
current_value,
self->priv->duration,
((double) current_value / self->priv->duration) * 100.0);
*/
g_signal_handlers_block_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment")), (self->priv->duration > 0) ? ((double) current_value / self->priv->duration) * 100.0 : 0.0);
g_signal_handlers_unblock_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
g_free (s);
}
}
static void
position_value_changed_cb (GtkAdjustment *adjustment,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
gint64 current_value;
char *s;
if (self->priv->playbin == NULL)
return;
current_value = (gint64) (gtk_adjustment_get_value (adjustment) / 100.0 * self->priv->duration);
gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET,
current_value,
GST_SEEK_TYPE_NONE,
0.0);
s = _g_format_duration_for_display (GST_TIME_AS_MSECONDS (current_value));
gtk_label_set_text (GTK_LABEL (GET_WIDGET ("label_position")), s);
g_free (s);
}
static void
update_playback_info (GthMediaViewerPage *self)
{
char *playback_info;
playback_info = g_strdup_printf ("@%2.2f", self->priv->rate);
g_file_info_set_attribute_string (gth_browser_get_current_file (self->priv->browser)->info, "pix::statusbar-extra-info", playback_info);
gth_browser_update_statusbar_file_info (self->priv->browser);
g_free (playback_info);
}
static void
update_player_rate (GthMediaViewerPage *self)
{
gint64 current_value;
self->priv->rate = CLAMP (self->priv->rate,
default_rates[0],
default_rates[G_N_ELEMENTS (default_rates) - 1]);
if (self->priv->playbin == NULL)
return;
update_playback_info (self);
if (! self->priv->playing)
return;
current_value = (gint64) (gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment"))) / 100.0 * self->priv->duration);
if (! gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET,
current_value,
GST_SEEK_TYPE_NONE,
0.0))
{
g_warning ("seek failed");
}
}
static void
play_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_toggle_play (GTH_MEDIA_VIEWER_PAGE (user_data));
}
static void
play_slower_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_play_slower (GTH_MEDIA_VIEWER_PAGE (user_data));
}
static void
play_faster_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_play_faster (GTH_MEDIA_VIEWER_PAGE (user_data));
}
static void
loop_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
self->priv->loop = ! self->priv->loop;
}
static void
position_button_toggled_cb (GtkButton *button,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("position_button")))) {
gtk_popover_popup (GTK_POPOVER (GET_WIDGET ("position_popover")));
gth_browser_keep_mouse_visible (self->priv->browser, TRUE);
}
}
static void
position_popover_closed_cb (GtkPopover *popover,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
gth_browser_keep_mouse_visible (self->priv->browser, FALSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("position_button")), FALSE);
}
static gboolean
update_volume_from_playbin (GthMediaViewerPage *self)
{
double volume, v;
gboolean mute;
if (self->priv->update_volume_id != 0) {
g_source_remove (self->priv->update_volume_id);
self->priv->update_volume_id = 0;
}
if ((self->priv->builder == NULL) || (self->priv->playbin == NULL))
return FALSE;
g_object_get (self->priv->playbin, "volume", &volume, "mute", &mute, NULL);
if (mute)
volume = 0;
/* cubic in [0,1], linear in [1,2] */
if (volume <= 1.0)
v = exp (1.0 / 3.0 * log (volume)); /* cube root of volume */
else
v = volume;
g_signal_handlers_block_by_func (GET_WIDGET ("volume_adjustment"), volume_value_changed_cb, self);
gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("volume_adjustment")), v * 100.0);
g_signal_handlers_unblock_by_func (GET_WIDGET ("volume_adjustment"), volume_value_changed_cb, self);
return FALSE;
}
static gboolean
update_progress_cb (gpointer user_data)
{
GthMediaViewerPage *self = user_data;
if (self->priv->update_progress_id != 0) {
g_source_remove (self->priv->update_progress_id);
self->priv->update_progress_id = 0;
}
update_current_position_bar (self);
self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
return FALSE;
}
static void
set_playing_state (GthMediaViewerPage *self,
gboolean playing)
{
self->priv->playing = playing;
if (self->priv->playing)
gth_screensaver_inhibit (self->priv->screensaver,
GTK_WINDOW (self->priv->browser),
_("Playing video"));
else
gth_screensaver_uninhibit (self->priv->screensaver);
}
static void
update_play_button (GthMediaViewerPage *self,
GstState new_state)
{
if (! self->priv->playing && (new_state == GST_STATE_PLAYING)) {
set_playing_state (self, TRUE);
gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_button_image")), "media-playback-pause-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
gtk_widget_set_tooltip_text (GET_WIDGET ("play_button_image"), _("Pause"));
if (self->priv->update_progress_id == 0)
self->priv->update_progress_id = gdk_threads_add_timeout (PROGRESS_DELAY, update_progress_cb, self);
update_playback_info (self);
}
else if (self->priv->playing && (new_state != GST_STATE_PLAYING)) {
GtkWidget *play_button = GET_WIDGET ("play_button_image");
set_playing_state (self, FALSE);
gtk_image_set_from_icon_name (GTK_IMAGE (play_button),
"media-playback-start-symbolic",
GTK_ICON_SIZE_LARGE_TOOLBAR);
gtk_widget_set_tooltip_text (GET_WIDGET ("play_button_image"), _("Play"));
if (self->priv->update_progress_id != 0) {
g_source_remove (self->priv->update_progress_id);
self->priv->update_progress_id = 0;
}
update_playback_info (self);
}
gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
}
/*
static char *
state_description (GstState state)
{
switch (state) {
case GST_STATE_VOID_PENDING:
return "void pending";
case GST_STATE_NULL:
return "null";
case GST_STATE_READY:
return "ready";
case GST_STATE_PAUSED:
return "paused";
case GST_STATE_PLAYING:
return "playing";
}
return "error";
}
*/
static void
reset_player_state (GthMediaViewerPage *self)
{
if (self->priv->update_progress_id != 0) {
g_source_remove (self->priv->update_progress_id);
self->priv->update_progress_id = 0;
}
update_play_button (self, GST_STATE_NULL);
self->priv->rate = 1.0;
set_playing_state (self, FALSE);
}
static void
update_stream_info (GthMediaViewerPage *self)
{
GstElement *audio_sink;
GstElement *video_sink;
GstPad *audio_pad;
GstPad *video_pad;
g_object_get (self->priv->playbin,
"audio-sink", &audio_sink,
"video-sink", &video_sink,
NULL);
self->priv->has_audio = FALSE;
self->priv->has_video = FALSE;
if (audio_sink != NULL) {
audio_pad = gst_element_get_static_pad (GST_ELEMENT (audio_sink), "sink");
if (audio_pad != NULL) {
GstCaps *caps;
if ((caps = gst_pad_get_current_caps (audio_pad)) != NULL) {
self->priv->has_audio = TRUE;
gst_caps_unref (caps);
}
}
}
if (video_sink != NULL) {
video_pad = gst_element_get_static_pad (GST_ELEMENT (video_sink), "sink");
if (video_pad != NULL) {
GstCaps *caps;
if ((caps = gst_pad_get_current_caps (video_pad)) != NULL) {
GstStructure *structure;
int video_width;
int video_height;
structure = gst_caps_get_structure (caps, 0);
gst_structure_get_fraction (structure, "framerate", &self->priv->video_fps_n, &self->priv->video_fps_d);
if (gst_structure_get_int (structure, "width", &video_width)
&& gst_structure_get_int (structure, "height", &video_height))
{
g_file_info_set_attribute_int32 (self->priv->updated_info, "frame::width", video_width);
g_file_info_set_attribute_int32 (self->priv->updated_info, "frame::height", video_height);
self->priv->has_video = TRUE;
self->priv->video_width = video_width;
self->priv->video_height = video_height;
}
gst_caps_unref (caps);
}
}
}
gtk_stack_set_visible_child_name (GTK_STACK (self->priv->area_box), self->priv->has_video ? "video-area" : "audio-area");
update_zoom_info (self);
}
static void
bus_message_cb (GstBus *bus,
GstMessage *message,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
if (GST_MESSAGE_SRC (message) != GST_OBJECT (self->priv->playbin))
return;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ASYNC_DONE: {
update_current_position_bar (self);
break;
}
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state;
GstState new_state;
GstState pending_state;
old_state = new_state = GST_STATE_NULL;
gst_message_parse_state_changed (message, &old_state, &new_state, &pending_state);
if (old_state == new_state)
break;
/*
g_print ("old state: %s\n", state_description (old_state));
g_print ("new state: %s\n", state_description (new_state));
g_print ("pending state: %s\n", state_description (pending_state));
g_print ("\n");
*/
self->priv->paused = (new_state == GST_STATE_PAUSED);
update_current_position_bar (self);
if ((old_state == GST_STATE_NULL) && (new_state == GST_STATE_READY) && (pending_state != GST_STATE_PAUSED)) {
update_stream_info (self);
gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, self->priv->updated_info, TRUE);
}
if ((old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED)) {
update_stream_info (self);
gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, self->priv->updated_info, TRUE);
}
if ((old_state == GST_STATE_READY) || (new_state == GST_STATE_PAUSED))
update_volume_from_playbin (self);
if ((old_state == GST_STATE_PLAYING) || (new_state == GST_STATE_PLAYING))
update_play_button (self, new_state);
break;
}
case GST_MESSAGE_DURATION_CHANGED:
self->priv->duration = 0;
update_current_position_bar (self);
break;
case GST_MESSAGE_EOS:
if (self->priv->loop && self->priv->playing)
gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET,
0.0,
GST_SEEK_TYPE_NONE,
0.0);
else
reset_player_state (self);
break;
case GST_MESSAGE_BUFFERING: {
int percent = 0;
gst_message_parse_buffering (message, &percent);
gst_element_set_state (self->priv->playbin, (percent == 100) ? GST_STATE_PLAYING : GST_STATE_PAUSED);
break;
}
case GST_MESSAGE_ERROR:
gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self), self->priv->file_data, NULL, FALSE);
break;
default:
break;
}
}
static void
playbin_notify_volume_cb (GObject *playbin,
GParamSpec *pspec,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
if (self->priv->update_volume_id == 0)
self->priv->update_volume_id = g_idle_add ((GSourceFunc) update_volume_from_playbin, self);
}
static void
create_playbin (GthMediaViewerPage *self)
{
GstElement *scaletempo;
gboolean sink_created;
GstBus *bus;
if (self->priv->playbin != NULL)
return;
self->priv->playbin = gst_element_factory_make ("playbin", "playbin");
scaletempo = gst_element_factory_make ("scaletempo", "");
if (scaletempo != NULL)
g_object_set (self->priv->playbin, "audio-filter", scaletempo, NULL);
sink_created = FALSE;
if (g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_USE_HARDWARE_ACCEL)) {
GstElement *gtkglsink;
gtkglsink = gst_element_factory_make ("gtkglsink", "sink");
if (gtkglsink != NULL) {
GstElement *glsinkbin;
glsinkbin = gst_element_factory_make ("glsinkbin", "");
if (glsinkbin != NULL) {
g_object_set (glsinkbin,
"enable-last-sample", TRUE,
"sink", gtkglsink,
NULL);
g_object_set (self->priv->playbin, "video-sink", glsinkbin, NULL);
g_object_get (gtkglsink, "widget", &self->priv->video_area, NULL);
sink_created = TRUE;
}
}
}
if (! sink_created) {
GstElement *gtksink;
gtksink = gst_element_factory_make ("gtksink", "sink");
g_object_set (self->priv->playbin, "video-sink", gtksink, NULL);
g_object_get (gtksink, "widget", &self->priv->video_area, NULL);
}
gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->video_area), "video-player");
gtk_widget_add_events (self->priv->video_area,
(gtk_widget_get_events (self->priv->video_area)
| GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_BUTTON_MOTION_MASK
| GDK_SCROLL_MASK));
gtk_widget_set_can_focus (self->priv->video_area, TRUE);
g_signal_connect (G_OBJECT (self->priv->video_area),
"realize",
G_CALLBACK (video_area_realize_cb),
self);
g_signal_connect (G_OBJECT (self->priv->video_area),
"unrealize",
G_CALLBACK (video_area_unrealize_cb),
self);
g_signal_connect (G_OBJECT (self->priv->video_area),
"size-allocate",
G_CALLBACK (video_area_size_allocate_cb),
self);
gtk_stack_add_named (GTK_STACK (self->priv->area_box), self->priv->video_area, "video-area");
gtk_widget_show (self->priv->video_area);
g_object_set (self->priv->playbin,
"volume", (double) g_settings_get_int (self->priv->settings, PREF_GSTREAMER_TOOLS_VOLUME) / 100.0,
"mute", g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_TOOLS_MUTE),
"force-aspect-ratio", TRUE,
NULL);
bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin));
gst_bus_add_signal_watch (bus);
g_signal_connect (self->priv->playbin,
"notify::volume",
G_CALLBACK (playbin_notify_volume_cb),
self);
g_signal_connect (self->priv->playbin,
"notify::mute",
G_CALLBACK (playbin_notify_volume_cb),
self);
g_signal_connect (bus,
"message",
G_CALLBACK (bus_message_cb),
self);
}
static void
skip_back_bigger_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), -60 * 5);
}
static void
skip_back_big_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), -60);
}
static void
skip_back_small_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), -10);
}
static void
skip_back_smaller_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), -5);
}
static void
skip_back_smallest_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), -1);
}
static void
skip_forward_smallest_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), 1);
}
static void
skip_forward_smaller_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), 5);
}
static void
skip_forward_small_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), 10);
}
static void
skip_forward_big_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), 60);
}
static void
skip_forward_bigger_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
gth_media_viewer_page_skip (GTH_MEDIA_VIEWER_PAGE (user_data), 60 * 5);
}
static void
copy_position_to_clipboard_button_clicked_cb (GtkButton *button,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
gint64 current_time = 0;
char *text;
if (! gst_element_query_position (self->priv->playbin, GST_FORMAT_TIME, ¤t_time))
return;
{
int sec, min, hour, _time;
_time = (int) GST_TIME_AS_SECONDS (current_time);
sec = _time % 60;
_time = _time - sec;
min = (_time % (60*60)) / 60;
_time = _time - (min * 60);
hour = _time / (60*60);
text = g_strdup_printf ("%d:%02d:%02d", hour, min, sec);
}
gtk_clipboard_set_text (gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (button)), GDK_SELECTION_CLIPBOARD), text, -1);
g_free (text);
}
#define SCALE_INTERNAL_PADDING 17
static void
update_time_popup_position (GthMediaViewerPage *self,
double x)
{
GdkRectangle rect;
GtkAllocation alloc;
double p;
char *s;
rect.x = x;
rect.y = 0;
rect.width = 1;
rect.height = 1;
gtk_widget_get_allocated_size (GET_WIDGET ("position_scale"), &alloc, NULL);
alloc.x = SCALE_INTERNAL_PADDING;
alloc.width -= SCALE_INTERNAL_PADDING;
if (rect.x < alloc.x)
rect.x = alloc.x;
if (rect.x > alloc.width)
rect.x = alloc.width;
gtk_popover_set_pointing_to (GTK_POPOVER (GET_WIDGET ("time_popover")), &rect);
p = (double) (rect.x - alloc.x) / (double) (alloc.width - alloc.x);
s = _g_format_duration_for_display (p * GST_TIME_AS_MSECONDS (self->priv->duration));
gtk_label_set_text (GTK_LABEL (GET_WIDGET ("time_popover_label")), s);
g_free (s);
}
static void
position_scale_enter_notify_event_cb (GtkWidget *widget,
GdkEventCrossing *event,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
update_time_popup_position (self, event->x);
gtk_popover_popup (GTK_POPOVER (GET_WIDGET ("time_popover")));
}
static void
position_scale_leave_notify_event_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
gtk_popover_popdown (GTK_POPOVER (GET_WIDGET ("time_popover")));
}
static void
position_scale_motion_notify_event_cb (GtkWidget *widget,
GdkEventMotion *event,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
update_time_popup_position (self, event->x);
}
static void
gth_media_viewer_page_real_activate (GthViewerPage *base,
GthBrowser *browser)
{
GthMediaViewerPage *self;
if (! gstreamer_init ())
return;
self = (GthMediaViewerPage*) base;
self->priv->browser = browser;
g_action_map_add_action_entries (G_ACTION_MAP (browser),
actions,
G_N_ELEMENTS (actions),
browser);
self->priv->screenshot_button =
gth_browser_add_header_bar_button (browser,
GTH_BROWSER_HEADER_SECTION_VIEWER_VIEW,
"camera-photo-symbolic",
_("Take a screenshot"),
"win.video-screenshot",
NULL);
self->priv->fit_button =
gth_browser_add_header_bar_toggle_button (browser,
GTH_BROWSER_HEADER_SECTION_VIEWER_ZOOM,
"view-zoom-fit-symbolic",
_("Fit to window"),
"win.video-zoom-fit",
NULL);
/* audio area */
self->priv->audio_area = gtk_drawing_area_new ();
gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->audio_area), "video-player");
gtk_widget_add_events (self->priv->audio_area, (gtk_widget_get_events (self->priv->audio_area)
| GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_BUTTON_MOTION_MASK
| GDK_SCROLL_MASK));
gtk_widget_set_can_focus (self->priv->audio_area, TRUE);
gtk_widget_show (self->priv->audio_area);
g_signal_connect (G_OBJECT (self->priv->audio_area),
"draw",
G_CALLBACK (video_area_draw_cb),
self);
/* mediabar */
self->priv->builder = _gtk_builder_new_from_file ("mediabar.ui", "gstreamer_tools");
self->priv->mediabar = GET_WIDGET ("mediabar");
gtk_widget_set_halign (self->priv->mediabar, GTK_ALIGN_FILL);
gtk_widget_set_valign (self->priv->mediabar, GTK_ALIGN_END);
gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_slower_image")),
"media-seek-backward-symbolic",
GTK_ICON_SIZE_MENU);
gtk_image_set_from_icon_name (GTK_IMAGE (GET_WIDGET ("play_faster_image")),
"media-seek-forward-symbolic",
GTK_ICON_SIZE_MENU);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("loop_button")), self->priv->loop);
g_signal_connect (GET_WIDGET ("volume_adjustment"),
"value-changed",
G_CALLBACK (volume_value_changed_cb),
self);
g_signal_connect (GET_WIDGET ("position_adjustment"),
"value-changed",
G_CALLBACK (position_value_changed_cb),
self);
g_signal_connect (GET_WIDGET ("play_button"),
"clicked",
G_CALLBACK (play_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("play_slower_button"),
"clicked",
G_CALLBACK (play_slower_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("play_faster_button"),
"clicked",
G_CALLBACK (play_faster_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("loop_button"),
"clicked",
G_CALLBACK (loop_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("position_button"),
"toggled",
G_CALLBACK (position_button_toggled_cb),
self);
g_signal_connect (GET_WIDGET ("position_popover"),
"closed",
G_CALLBACK (position_popover_closed_cb),
self);
g_signal_connect (GET_WIDGET ("skip_back_bigger_button"),
"clicked",
G_CALLBACK (skip_back_bigger_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_back_big_button"),
"clicked",
G_CALLBACK (skip_back_big_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_back_small_button"),
"clicked",
G_CALLBACK (skip_back_small_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_back_smaller_button"),
"clicked",
G_CALLBACK (skip_back_smaller_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_back_smallest_button"),
"clicked",
G_CALLBACK (skip_back_smallest_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_forward_smallest_button"),
"clicked",
G_CALLBACK (skip_forward_smallest_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_forward_smaller_button"),
"clicked",
G_CALLBACK (skip_forward_smaller_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_forward_small_button"),
"clicked",
G_CALLBACK (skip_forward_small_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_forward_big_button"),
"clicked",
G_CALLBACK (skip_forward_big_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("skip_forward_bigger_button"),
"clicked",
G_CALLBACK (skip_forward_bigger_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("copy_position_to_clipboard_button"),
"clicked",
G_CALLBACK (copy_position_to_clipboard_button_clicked_cb),
self);
g_signal_connect (GET_WIDGET ("position_scale"),
"enter-notify-event",
G_CALLBACK (position_scale_enter_notify_event_cb),
self);
g_signal_connect (GET_WIDGET ("position_scale"),
"leave-notify-event",
G_CALLBACK (position_scale_leave_notify_event_cb),
self);
g_signal_connect (GET_WIDGET ("position_scale"),
"motion-notify-event",
G_CALLBACK (position_scale_motion_notify_event_cb),
self);
self->priv->mediabar_revealer = gtk_revealer_new ();
gtk_revealer_set_transition_type (GTK_REVEALER (self->priv->mediabar_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
gtk_widget_set_halign (self->priv->mediabar_revealer, GTK_ALIGN_FILL);
gtk_widget_set_valign (self->priv->mediabar_revealer, GTK_ALIGN_END);
gtk_widget_show (self->priv->mediabar_revealer);
gtk_container_add (GTK_CONTAINER (self->priv->mediabar_revealer), self->priv->mediabar);
self->priv->area_box = gtk_stack_new ();
gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->area_box), "video-player");
gtk_stack_add_named (GTK_STACK (self->priv->area_box), self->priv->audio_area, "audio-area");
gtk_widget_show (self->priv->area_box);
g_signal_connect (G_OBJECT (self->priv->area_box),
"button_press_event",
G_CALLBACK (video_area_button_press_cb),
self);
g_signal_connect (G_OBJECT (self->priv->area_box),
"popup-menu",
G_CALLBACK (video_area_popup_menu_cb),
self);
g_signal_connect (G_OBJECT (self->priv->area_box),
"scroll_event",
G_CALLBACK (video_area_scroll_event_cb),
self);
self->priv->area_overlay = gtk_overlay_new ();
gtk_container_add (GTK_CONTAINER (self->priv->area_overlay), self->priv->area_box);
gtk_overlay_add_overlay (GTK_OVERLAY (self->priv->area_overlay), self->priv->mediabar_revealer);
gtk_widget_show (self->priv->area_overlay);
gth_browser_set_viewer_widget (browser, self->priv->area_overlay);
gtk_widget_realize (self->priv->audio_area);
gth_browser_register_viewer_control (self->priv->browser, self->priv->mediabar_revealer);
gth_browser_register_viewer_control (self->priv->browser, gtk_scale_button_get_popup (GTK_SCALE_BUTTON (GET_WIDGET ("volumebutton"))));
gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
create_playbin (self);
gth_media_viewer_page_set_fit_if_larger (self, g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT));
}
static void
wait_playbin_state_change_to_complete (GthMediaViewerPage *self)
{
(void) gst_element_get_state (self->priv->playbin,
NULL,
NULL,
GST_SECOND * 10);
}
static void
gth_media_viewer_page_real_deactivate (GthViewerPage *base)
{
GthMediaViewerPage *self;
self = (GthMediaViewerPage*) base;
gth_browser_unregister_viewer_control (self->priv->browser, gtk_scale_button_get_popup (GTK_SCALE_BUTTON (GET_WIDGET ("volumebutton"))));
gth_browser_unregister_viewer_control (self->priv->browser, self->priv->mediabar_revealer);
if (self->priv->builder != NULL) {
g_object_unref (self->priv->builder);
self->priv->builder = NULL;
}
if (self->priv->update_progress_id != 0) {
g_source_remove (self->priv->update_progress_id);
self->priv->update_progress_id = 0;
}
if (self->priv->update_volume_id != 0) {
g_source_remove (self->priv->update_volume_id);
self->priv->update_volume_id = 0;
}
if (self->priv->playbin != NULL) {
double volume;
gboolean mute;
g_object_get (self->priv->playbin, "volume", &volume, "mute", &mute, NULL);
g_settings_set_int (self->priv->settings, PREF_GSTREAMER_TOOLS_VOLUME, (int) (volume * 100.0));
g_settings_set_boolean (self->priv->settings, PREF_GSTREAMER_TOOLS_MUTE, mute);
g_settings_set_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT, self->priv->fit_if_larger);
_g_signal_handlers_disconnect_by_data (self->priv->playbin, self);
_g_signal_handlers_disconnect_by_data (self->priv->video_area, self);
gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
wait_playbin_state_change_to_complete (self);
gst_object_unref (GST_OBJECT (self->priv->playbin));
self->priv->playbin = NULL;
self->priv->video_area = NULL;
self->priv->audio_area = NULL;
}
gtk_widget_destroy (self->priv->screenshot_button);
gtk_widget_destroy (self->priv->fit_button);
self->priv->screenshot_button = NULL;
self->priv->fit_button = NULL;
gth_browser_set_viewer_widget (self->priv->browser, NULL);
}
static void
_gth_media_viewer_page_set_uri (GthMediaViewerPage *self,
const char *uri,
GstState state)
{
GstElement *videoflip;
g_return_if_fail (self->priv->playbin != NULL);
gst_element_set_state (self->priv->playbin, GST_STATE_NULL);
/* Re-create videoflip every time because the auto-mode will remember
the last-seen orientation tag and will happily use it for a new
file that does not contain any orientation tag. */
g_object_get (self->priv->playbin, "video-filter", &videoflip, NULL);
if (videoflip != NULL) {
gst_object_unref (GST_OBJECT (videoflip));
videoflip = NULL;
}
videoflip = gst_element_factory_make ("videoflip", "");
g_object_set (videoflip, "video-direction", GST_VIDEO_ORIENTATION_AUTO, NULL);
g_object_set (self->priv->playbin, "video-filter", videoflip, NULL);
g_object_set (G_OBJECT (self->priv->playbin), "uri", uri, NULL);
gst_element_set_state (self->priv->playbin, state);
wait_playbin_state_change_to_complete (self);
}
static void
gth_media_viewer_page_real_show (GthViewerPage *base)
{
GthMediaViewerPage *self = GTH_MEDIA_VIEWER_PAGE (base);
self->priv->visible = TRUE;
self->priv->background_painted = FALSE;
gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
if (self->priv->file_data != NULL) {
char *uri;
uri = g_file_get_uri (self->priv->file_data->file);
_gth_media_viewer_page_set_uri (self, uri, GST_STATE_PLAYING);
g_free (uri);
}
}
static void
gth_media_viewer_page_real_hide (GthViewerPage *base)
{
GthMediaViewerPage *self;
self = (GthMediaViewerPage*) base;
self->priv->visible = FALSE;
if ((self->priv->playbin != NULL) && self->priv->playing)
gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
}
static gboolean
gth_media_viewer_page_real_can_view (GthViewerPage *base,
GthFileData *file_data)
{
g_return_val_if_fail (file_data != NULL, FALSE);
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));
}
static void
gth_media_viewer_page_real_view (GthViewerPage *base,
GthFileData *file_data)
{
GthMediaViewerPage *self;
char *uri;
self = (GthMediaViewerPage*) base;
g_return_if_fail (file_data != NULL);
g_return_if_fail (self->priv->playbin != NULL);
gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
if ((self->priv->file_data != NULL)
&& g_file_equal (file_data->file, self->priv->file_data->file)
&& (gth_file_data_get_mtime (file_data) == gth_file_data_get_mtime (self->priv->file_data)))
{
return;
}
/**/
_g_object_unref (self->priv->file_data);
_g_object_unref (self->priv->updated_info);
self->priv->file_data = gth_file_data_dup (file_data);
self->priv->updated_info = g_file_info_new ();
self->priv->duration = 0;
self->priv->has_audio = FALSE;
self->priv->has_video = FALSE;
self->priv->background_painted = FALSE;
_g_object_unref (self->priv->icon);
self->priv->icon = NULL;
_gth_media_viewer_page_update_caption (self);
/**/
g_signal_handlers_block_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment")), 0.0);
g_signal_handlers_unblock_by_func(GET_WIDGET ("position_adjustment"), position_value_changed_cb, self);
reset_player_state (self);
uri = g_file_get_uri (self->priv->file_data->file);
_gth_media_viewer_page_set_uri (self, uri, self->priv->visible ? GST_STATE_PLAYING : GST_STATE_PAUSED);
g_free (uri);
}
static void
gth_media_viewer_page_real_focus (GthViewerPage *base)
{
GthMediaViewerPage *self = (GthMediaViewerPage*) base;
GtkWidget *widget;
widget = NULL;
if (self->priv->has_video)
widget = self->priv->video_area;
else if (self->priv->has_audio)
widget = self->priv->audio_area;
if ((widget != NULL) && gtk_widget_get_realized (widget) && gtk_widget_get_mapped (widget))
gtk_widget_grab_focus (widget);
}
static void
gth_media_viewer_page_real_fullscreen (GthViewerPage *base,
gboolean active)
{
/* void */
}
static void
gth_media_viewer_page_real_show_pointer (GthViewerPage *base,
gboolean show)
{
GthMediaViewerPage *self = (GthMediaViewerPage*) base;
if (show == self->priv->cursor_visible)
return;
self->priv->cursor_visible = show;
if (self->priv->video_area != NULL) {
if (show && (self->priv->cursor != NULL))
gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor);
if (! show && gth_browser_get_is_fullscreen (self->priv->browser) && (self->priv->cursor_void != NULL))
gdk_window_set_cursor (gtk_widget_get_window (self->priv->video_area), self->priv->cursor_void);
}
gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->mediabar_revealer), show);
}
static void
gth_media_viewer_page_real_update_sensitivity (GthViewerPage *base)
{
GthMediaViewerPage *self = (GthMediaViewerPage *) base;
gtk_widget_set_sensitive (GET_WIDGET ("volume_box"), self->priv->has_audio);
gtk_widget_set_sensitive (GET_WIDGET ("play_button"), self->priv->has_video || self->priv->has_audio);
gth_window_enable_action (GTH_WINDOW (self->priv->browser), "video-screenshot", self->priv->has_video);
gth_window_enable_action (GTH_WINDOW (self->priv->browser), "video-zoom-fit", self->priv->has_video);
}
static gboolean
gth_media_viewer_page_real_can_save (GthViewerPage *base)
{
return FALSE;
}
static void
gth_media_viewer_page_real_save (GthViewerPage *base,
GFile *file,
FileSavedFunc func,
gpointer user_data)
{
/* void */
}
static void
gth_media_viewer_page_real_save_as (GthViewerPage *base,
FileSavedFunc func,
gpointer user_data)
{
/* void */
}
static void
gth_media_viewer_page_real_revert (GthViewerPage *base)
{
/* void */
}
static void
gth_media_viewer_page_real_update_info (GthViewerPage *base,
GthFileData *file_data)
{
GthMediaViewerPage *self = GTH_MEDIA_VIEWER_PAGE (base);
if (! _g_file_equal (self->priv->file_data->file, file_data->file))
return;
_g_object_unref (self->priv->file_data);
self->priv->file_data = gth_file_data_dup (file_data);
}
static const char *
gth_media_viewer_page_shortcut_context (GthViewerPage *base)
{
return GTH_SHORTCUT_VIEWER_CONTEXT_MEDIA;
}
static void
gth_media_viewer_page_finalize (GObject *obj)
{
GthMediaViewerPage *self;
self = GTH_MEDIA_VIEWER_PAGE (obj);
if (self->priv->update_progress_id != 0) {
g_source_remove (self->priv->update_progress_id);
self->priv->update_progress_id = 0;
}
if (self->priv->update_volume_id != 0) {
g_source_remove (self->priv->update_volume_id);
self->priv->update_volume_id = 0;
}
_g_object_unref (self->priv->icon);
_g_object_unref (self->priv->file_data);
_g_object_unref (self->priv->updated_info);
if (self->priv->screensaver != NULL) {
gth_screensaver_uninhibit (self->priv->screensaver);
g_object_unref (self->priv->screensaver);
}
_g_object_unref (self->priv->settings);
G_OBJECT_CLASS (gth_media_viewer_page_parent_class)->finalize (obj);
}
static void
gth_media_viewer_page_class_init (GthMediaViewerPageClass *klass)
{
G_OBJECT_CLASS (klass)->finalize = gth_media_viewer_page_finalize;
}
static void
gth_viewer_page_interface_init (GthViewerPageInterface *iface)
{
iface->activate = gth_media_viewer_page_real_activate;
iface->deactivate = gth_media_viewer_page_real_deactivate;
iface->show = gth_media_viewer_page_real_show;
iface->hide = gth_media_viewer_page_real_hide;
iface->can_view = gth_media_viewer_page_real_can_view;
iface->view = gth_media_viewer_page_real_view;
iface->focus = gth_media_viewer_page_real_focus;
iface->fullscreen = gth_media_viewer_page_real_fullscreen;
iface->show_pointer = gth_media_viewer_page_real_show_pointer;
iface->update_sensitivity = gth_media_viewer_page_real_update_sensitivity;
iface->can_save = gth_media_viewer_page_real_can_save;
iface->save = gth_media_viewer_page_real_save;
iface->save_as = gth_media_viewer_page_real_save_as;
iface->revert = gth_media_viewer_page_real_revert;
iface->update_info = gth_media_viewer_page_real_update_info;
iface->shortcut_context = gth_media_viewer_page_shortcut_context;
}
static void
pref_zoom_to_fit_changed (GSettings *settings,
char *key,
gpointer user_data)
{
GthMediaViewerPage *self = user_data;
gth_media_viewer_page_set_fit_if_larger (self, g_settings_get_boolean (self->priv->settings, PREF_GSTREAMER_ZOOM_TO_FIT));
}
static void
gth_media_viewer_page_init (GthMediaViewerPage *self)
{
self->priv = gth_media_viewer_page_get_instance_private (self);
self->priv->settings = g_settings_new (PIX_GSTREAMER_TOOLS_SCHEMA);
self->priv->update_progress_id = 0;
self->priv->update_volume_id = 0;
self->priv->has_video = FALSE;
self->priv->has_audio = FALSE;
self->priv->video_fps_n = 0;
self->priv->video_fps_d = 0;
self->priv->icon = NULL;
self->priv->cursor_visible = TRUE;
self->priv->screensaver = gth_screensaver_new (NULL);
self->priv->visible = FALSE;
self->priv->screenshot_button = NULL;
self->priv->background_painted = FALSE;
self->priv->file_data = NULL;
self->priv->updated_info = NULL;
self->priv->loop = FALSE;
self->priv->fit_if_larger = TRUE;
/* settings notifications */
g_signal_connect (self->priv->settings,
"changed::" PREF_GSTREAMER_ZOOM_TO_FIT,
G_CALLBACK (pref_zoom_to_fit_changed),
self);
}
GthBrowser *
gth_media_viewer_page_get_browser (GthMediaViewerPage *self)
{
return self->priv->browser;
}
GstElement *
gth_media_viewer_page_get_playbin (GthMediaViewerPage *self)
{
return self->priv->playbin;
}
gboolean
gth_media_viewer_page_is_playing (GthMediaViewerPage *self)
{
return self->priv->playing;
}
void
gth_media_viewer_page_get_video_fps (GthMediaViewerPage *self,
int *video_fps_n,
int *video_fps_d)
{
if (video_fps_n != NULL)
*video_fps_n = self->priv->video_fps_n;
if (video_fps_d != NULL)
*video_fps_d = self->priv->video_fps_d;
}
GthFileData *
gth_media_viewer_page_get_file_data (GthMediaViewerPage *self)
{
return self->priv->file_data;
}
static gint64
_gth_media_viewer_page_get_current_time (GthMediaViewerPage *self)
{
return (gint64) (gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("position_adjustment"))) / 100.0 * self->priv->duration);
}
void
gth_media_viewer_page_toggle_play (GthMediaViewerPage *self)
{
if (self->priv->playbin == NULL)
return;
if (! self->priv->playing) {
if (! self->priv->paused) {
gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET,
0.0,
GST_SEEK_TYPE_NONE,
0.0);
}
else {
gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET,
_gth_media_viewer_page_get_current_time (self),
GST_SEEK_TYPE_NONE,
0.0);
}
gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING);
}
else
gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED);
}
void
gth_media_viewer_page_set_fit_if_larger (GthMediaViewerPage *self,
gboolean fit_if_larger)
{
GtkAlign alignment;
self->priv->fit_if_larger = fit_if_larger;
if (self->priv->video_area != NULL) {
alignment = self->priv->fit_if_larger ? GTK_ALIGN_FILL : GTK_ALIGN_CENTER;
gtk_widget_set_valign (self->priv->video_area, alignment);
gtk_widget_set_halign (self->priv->video_area, alignment);
gth_window_change_action_state (GTH_WINDOW (self->priv->browser), "video-zoom-fit", self->priv->fit_if_larger);
}
}
void
gth_media_viewer_page_skip (GthMediaViewerPage *self,
int seconds)
{
GstSeekFlags seek_flags;
GstSeekType start_type;
gint64 start;
if (self->priv->playbin == NULL)
return;
seek_flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE;
start_type = GST_SEEK_TYPE_SET;
start = _gth_media_viewer_page_get_current_time (self) + (seconds * GST_SECOND);
if (start < 0)
start = 0;
if (start >= self->priv->duration) {
seek_flags |= GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_BEFORE | GST_SEEK_FLAG_TRICKMODE;
start_type = GST_SEEK_TYPE_END;
start = 0;
}
gst_element_seek (self->priv->playbin,
self->priv->rate,
GST_FORMAT_TIME,
seek_flags,
start_type,
start,
GST_SEEK_TYPE_NONE,
0.0);
}
void
gth_media_viewer_page_next_frame (GthMediaViewerPage *self)
{
if (self->priv->playbin == NULL)
return;
if (! self->priv->has_video)
return;
gst_element_send_event (self->priv->playbin, gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (self->priv->rate), TRUE, FALSE));
}
void
gth_media_viewer_page_toggle_mute (GthMediaViewerPage *self)
{
gboolean mute;
if (self->priv->playbin == NULL)
return;
g_object_get (self->priv->playbin, "mute", &mute, NULL);
g_object_set (self->priv->playbin, "mute", ! mute, NULL);
}
static int
get_nearest_rate (double rate)
{
int min_idx = -1;
double min_delta = 0;
int i;
for (i = 0; i < G_N_ELEMENTS (default_rates); i++) {
double delta;
delta = fabs (default_rates[i] - rate);
if ((i == 0) || (delta < min_delta)) {
min_delta = delta;
min_idx = i;
}
}
return min_idx;
}
void
gth_media_viewer_page_play_faster (GthMediaViewerPage *self)
{
int i;
i = get_nearest_rate (self->priv->rate);
if (i < G_N_ELEMENTS (default_rates) - 1)
self->priv->rate = default_rates[i + 1];
else
self->priv->rate = default_rates[G_N_ELEMENTS (default_rates) - 1];
update_player_rate (self);
}
void
gth_media_viewer_page_play_slower (GthMediaViewerPage *self)
{
int i;
i = get_nearest_rate (self->priv->rate);
if (i > 0)
self->priv->rate = default_rates[i - 1];
else
self->priv->rate = default_rates[0];
update_player_rate (self);
}