aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2013-07-19 16:14:24 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2013-07-19 16:14:24 +0200
commitfa36afd4b61811fbce8e7bb8b41a9cf5e902077c (patch)
tree874902f146d7988c188f364414bb9dfa650362e6 /src
parent52a0430d6331d6e0ed4737832b402cd07f7010e4 (diff)
downloadgtk-vlc-player-fa36afd4b61811fbce8e7bb8b41a9cf5e902077c.tar.gz
moved lib/gtk-vlc-player to src/
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am28
-rw-r--r--src/cclosure-marshallers.list2
-rw-r--r--src/gtk-vlc-player-catalog.xml17
-rw-r--r--src/gtk-vlc-player.c864
-rw-r--r--src/gtk-vlc-player.h117
5 files changed, 1028 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..6dd424b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,28 @@
+AM_CFLAGS = -Wall
+
+BUILT_SOURCES = cclosure-marshallers.c cclosure-marshallers.h
+
+lib_LTLIBRARIES = libgtk-vlc-player.la
+libgtk_vlc_player_la_SOURCES = gtk-vlc-player.c gtk-vlc-player.h
+nodist_libgtk_vlc_player_la_SOURCES = $(BUILT_SOURCES)
+
+libgtk_vlc_player_la_CFLAGS = $(AM_CFLAGS) \
+ @LIBGTK_CFLAGS@ @LIBVLC_CFLAGS@
+libgtk_vlc_player_la_LIBADD = @LIBGTK_LIBS@ @LIBVLC_LIBS@
+libgtk_vlc_player_la_LDFLAGS = -no-undefined -shared -bindir @bindir@ \
+ -avoid-version
+
+include_HEADERS = gtk-vlc-player.h
+
+dist_gtk_vlc_player_catalogs_DATA = gtk-vlc-player-catalog.xml
+
+dist_noinst_DATA = cclosure-marshallers.list
+CLEANFILES = $(BUILT_SOURCES)
+
+MARSHAL_PREFIX = gtk_vlc_player_marshal
+
+cclosure-marshallers.c : cclosure-marshallers.list
+ @GLIB_GENMARSHAL@ --prefix $(MARSHAL_PREFIX) --body $< >$@
+
+cclosure-marshallers.h : cclosure-marshallers.list
+ @GLIB_GENMARSHAL@ --prefix $(MARSHAL_PREFIX) --header $< >$@
diff --git a/src/cclosure-marshallers.list b/src/cclosure-marshallers.list
new file mode 100644
index 0000000..6ed01a2
--- /dev/null
+++ b/src/cclosure-marshallers.list
@@ -0,0 +1,2 @@
+# Standard marshallers for "time-changed" and "length-changed" signal callbacks
+VOID:INT64
diff --git a/src/gtk-vlc-player-catalog.xml b/src/gtk-vlc-player-catalog.xml
new file mode 100644
index 0000000..66b928e
--- /dev/null
+++ b/src/gtk-vlc-player-catalog.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Install into $GLADE_CATALOG_PATH (default: /usr/share/glade3/catalogs)
+-->
+
+<glade-catalog name="gtk-vlc-player" library="gtk-vlc-player" depends="gtk+">
+ <glade-widget-classes>
+ <glade-widget-class name="GtkVlcPlayer"
+ generic-name="gtk-vlc-player"
+ title="VLC Player"/>
+ </glade-widget-classes>
+ <glade-widget-group name="gtk-vlc-player-widgets"
+ title="VLC Player Widgets">
+ <glade-widget-class-ref name="GtkVlcPlayer"/>
+ </glade-widget-group>
+</glade-catalog>
diff --git a/src/gtk-vlc-player.c b/src/gtk-vlc-player.c
new file mode 100644
index 0000000..0c80bf6
--- /dev/null
+++ b/src/gtk-vlc-player.c
@@ -0,0 +1,864 @@
+/**
+ * @file
+ * Simple \e GtkVlcPlayer widget for playing media files via libVLC.
+ * It supports fullscreen mode, a simple API, emitting signals on position and
+ * length changes, as well as \e GtkAdjustment support for connecting the player
+ * with scales and other widgets easily.
+ */
+
+/*
+ * Copyright (C) 2012-2013 Otto-von-Guericke-Universität Magdeburg
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#include <winuser.h>
+#endif
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#ifdef G_OS_WIN32
+#include <gdk/gdkwin32.h>
+#else
+#include <gdk/gdkx.h>
+#endif
+
+#include <vlc/vlc.h>
+#include <vlc/libvlc_version.h>
+
+#include "cclosure-marshallers.h"
+#include "gtk-vlc-player.h"
+
+static void gtk_vlc_player_class_init(GtkVlcPlayerClass *klass);
+static inline libvlc_instance_t *create_vlc_instance(void);
+static void gtk_vlc_player_init(GtkVlcPlayer *klass);
+
+static void gtk_vlc_player_dispose(GObject *gobject);
+static void gtk_vlc_player_finalize(GObject *gobject);
+
+static inline void maybe_lock_gdk(void);
+static inline void maybe_unlock_gdk(void);
+
+#ifdef G_OS_WIN32
+static BOOL CALLBACK enumerate_vlc_windows_cb(HWND hWndvlc, LPARAM lParam);
+static gboolean poll_vlc_event_window_cb(gpointer data);
+#endif
+static void widget_on_realize(GtkWidget *widget, gpointer data);
+static gboolean widget_on_click(GtkWidget *widget, GdkEventButton *event,
+ gpointer data);
+
+static void time_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data);
+static void time_adj_on_changed(GtkAdjustment *adj, gpointer user_data);
+static void vol_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data);
+
+static inline void set_transient_toplevel_window(GtkWindow *target,
+ GtkWidget *widget);
+
+static void update_time(GtkVlcPlayer *player, gint64 new_time);
+static void update_length(GtkVlcPlayer *player, gint64 new_length);
+
+static void vlc_time_changed(const struct libvlc_event_t *event,
+ void *userdata);
+static void vlc_length_changed(const struct libvlc_event_t *event,
+ void *userdata);
+
+static void vlc_player_load_media(GtkVlcPlayer *player, libvlc_media_t *media);
+
+/** @private */
+#define POLL_VLC_EVENT_WINDOW_INTERVAL 100 /* milliseconds */
+
+/** @private */
+#define GOBJECT_UNREF_SAFE(VAR) G_STMT_START { \
+ if ((VAR) != NULL) { \
+ g_object_unref(VAR); \
+ VAR = NULL; \
+ } \
+} G_STMT_END
+
+/** @private */
+#define GTK_VLC_PLAYER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), GTK_TYPE_VLC_PLAYER, GtkVlcPlayerPrivate))
+
+/** @private */
+struct _GtkVlcPlayerPrivate {
+ GtkObject *time_adjustment;
+ gulong time_adj_on_value_changed_id;
+ gulong time_adj_on_changed_id;
+
+ GtkObject *volume_adjustment;
+ gulong vol_adj_on_value_changed_id;
+
+ libvlc_instance_t *vlc_inst;
+ libvlc_media_player_t *media_player;
+
+ gboolean isFullscreen;
+ GtkWidget *fullscreen_window;
+};
+
+/** @private */
+enum {
+ TIME_CHANGED_SIGNAL,
+ LENGTH_CHANGED_SIGNAL,
+ LAST_SIGNAL
+};
+static guint gtk_vlc_player_signals[LAST_SIGNAL] = {0, 0};
+
+/**
+ * @private
+ * Will create \e gtk_vlc_player_get_type and set
+ * \e gtk_vlc_player_parent_class
+ */
+G_DEFINE_TYPE(GtkVlcPlayer, gtk_vlc_player, GTK_TYPE_ALIGNMENT);
+
+static void
+gtk_vlc_player_class_init(GtkVlcPlayerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->dispose = gtk_vlc_player_dispose;
+ gobject_class->finalize = gtk_vlc_player_finalize;
+
+ gtk_vlc_player_signals[TIME_CHANGED_SIGNAL] =
+ g_signal_new("time-changed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(GtkVlcPlayerClass, time_changed),
+ NULL, NULL,
+ gtk_vlc_player_marshal_VOID__INT64,
+ G_TYPE_NONE, 1, G_TYPE_INT64);
+
+ gtk_vlc_player_signals[LENGTH_CHANGED_SIGNAL] =
+ g_signal_new("length-changed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(GtkVlcPlayerClass, length_changed),
+ NULL, NULL,
+ gtk_vlc_player_marshal_VOID__INT64,
+ G_TYPE_NONE, 1, G_TYPE_INT64);
+
+ g_type_class_add_private(klass, sizeof(GtkVlcPlayerPrivate));
+}
+
+static inline libvlc_instance_t *
+create_vlc_instance(void)
+{
+ gchar **vlc_argv;
+ gint vlc_argc = 0;
+
+ libvlc_instance_t *ret;
+
+ vlc_argv = g_malloc_n(2, sizeof(vlc_argv[0]));
+ vlc_argv[vlc_argc++] = g_strdup(g_get_prgname());
+
+#if LIBVLC_VERSION_INT < LIBVLC_VERSION(2,0,0,0)
+ if (g_getenv("VLC_PLUGIN_PATH") != NULL) {
+ vlc_argv = g_realloc_n(vlc_argv,
+ vlc_argc + 2 + 1, sizeof(vlc_argv[0]));
+ vlc_argv[vlc_argc++] = g_strdup("--plugin-path");
+ vlc_argv[vlc_argc++] = g_strdup(g_getenv("VLC_PLUGIN_PATH"));
+ }
+#endif
+
+ vlc_argv[vlc_argc] = NULL;
+
+ ret = libvlc_new((int)vlc_argc, (const char *const *)vlc_argv);
+
+ g_strfreev(vlc_argv);
+ return ret;
+}
+
+static void
+gtk_vlc_player_init(GtkVlcPlayer *klass)
+{
+ GtkWidget *drawing_area;
+ GdkColor color;
+ libvlc_event_manager_t *evman;
+
+ klass->priv = GTK_VLC_PLAYER_GET_PRIVATE(klass);
+ gtk_alignment_set(GTK_ALIGNMENT(klass), 0., 0., 1., 1.);
+
+ drawing_area = gtk_drawing_area_new();
+
+ gdk_color_parse("black", &color);
+ gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &color);
+
+ g_signal_connect(G_OBJECT(drawing_area), "realize",
+ G_CALLBACK(widget_on_realize), klass);
+
+ gtk_widget_add_events(drawing_area, GDK_BUTTON_PRESS_MASK);
+ g_signal_connect(G_OBJECT(drawing_area), "button-press-event",
+ G_CALLBACK(widget_on_click), klass);
+
+ gtk_container_add(GTK_CONTAINER(klass), drawing_area);
+ gtk_widget_show(drawing_area);
+ /*
+ * drawing area will be destroyed automatically with the
+ * GtkContainer/GtkVlcPlayer
+ * (it is derived from GtkObject and has one reference after adding it
+ * to the container).
+ */
+
+ klass->priv->time_adjustment = gtk_adjustment_new(0., 0., 0.,
+ GTK_VLC_PLAYER_TIME_ADJ_STEP,
+ GTK_VLC_PLAYER_TIME_ADJ_PAGE,
+ 0.);
+ g_object_ref_sink(klass->priv->time_adjustment);
+ klass->priv->time_adj_on_value_changed_id =
+ g_signal_connect(G_OBJECT(klass->priv->time_adjustment),
+ "value-changed",
+ G_CALLBACK(time_adj_on_value_changed), klass);
+ klass->priv->time_adj_on_changed_id =
+ g_signal_connect(G_OBJECT(klass->priv->time_adjustment),
+ "changed",
+ G_CALLBACK(time_adj_on_changed), klass);
+
+ klass->priv->volume_adjustment = gtk_adjustment_new(1., 0., 1.,
+ GTK_VLC_PLAYER_VOL_ADJ_STEP,
+ GTK_VLC_PLAYER_VOL_ADJ_PAGE,
+ 0.);
+ g_object_ref_sink(klass->priv->volume_adjustment);
+ klass->priv->vol_adj_on_value_changed_id =
+ g_signal_connect(G_OBJECT(klass->priv->volume_adjustment),
+ "value-changed",
+ G_CALLBACK(vol_adj_on_value_changed), klass);
+
+ klass->priv->vlc_inst = create_vlc_instance();
+ klass->priv->media_player = libvlc_media_player_new(klass->priv->vlc_inst);
+
+ /* sign up for time updates */
+ evman = libvlc_media_player_event_manager(klass->priv->media_player);
+
+ libvlc_event_attach(evman, libvlc_MediaPlayerTimeChanged,
+ vlc_time_changed, klass);
+ libvlc_event_attach(evman, libvlc_MediaPlayerLengthChanged,
+ vlc_length_changed, klass);
+
+ klass->priv->isFullscreen = FALSE;
+ klass->priv->fullscreen_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_object_ref_sink(klass->priv->fullscreen_window);
+
+ gtk_window_set_deletable(GTK_WINDOW(klass->priv->fullscreen_window),
+ FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(klass->priv->fullscreen_window),
+ FALSE);
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(klass->priv->fullscreen_window),
+ TRUE);
+ gtk_window_set_skip_pager_hint(GTK_WINDOW(klass->priv->fullscreen_window),
+ TRUE);
+ gtk_window_set_keep_above(GTK_WINDOW(klass->priv->fullscreen_window),
+ TRUE);
+}
+
+static void
+gtk_vlc_player_dispose(GObject *gobject)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(gobject);
+
+ /*
+ * destroy might be called more than once, but we have only one
+ * reference for each object
+ */
+ if (player->priv->time_adjustment != NULL) {
+ g_signal_handler_disconnect(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_changed_id);
+ g_signal_handler_disconnect(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_value_changed_id);
+ g_object_unref(player->priv->time_adjustment);
+ player->priv->time_adjustment = NULL;
+ }
+ if (player->priv->volume_adjustment != NULL) {
+ g_signal_handler_disconnect(G_OBJECT(player->priv->volume_adjustment),
+ player->priv->vol_adj_on_value_changed_id);
+ g_object_unref(player->priv->volume_adjustment);
+ player->priv->volume_adjustment = NULL;
+ }
+ GOBJECT_UNREF_SAFE(player->priv->fullscreen_window);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS(gtk_vlc_player_parent_class)->dispose(gobject);
+}
+
+static void
+gtk_vlc_player_finalize(GObject *gobject)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(gobject);
+
+ libvlc_media_player_release(player->priv->media_player);
+ libvlc_release(player->priv->vlc_inst);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS(gtk_vlc_player_parent_class)->finalize(gobject);
+}
+
+/**
+ * @brief Locks GDK mutex if necessary.
+ *
+ * When GTK+ functions are invoked from another than the main
+ * thread (the one with the \e gtk_main() event loop),
+ * \e gdk_threads_enter() must be called.
+ * This auxiliary function is for callers (like VLC callbacks) that
+ * may or may not be invoked from the main event loop to avoid dead locks.
+ */
+static inline void
+maybe_lock_gdk(void)
+{
+ if (!g_main_context_is_owner(g_main_context_default()))
+ gdk_threads_enter();
+}
+
+/**
+ * @brief Unlocks GDK mutex if necessary.
+ *
+ * @see maybe_lock_gdk
+ */
+static inline void
+maybe_unlock_gdk(void)
+{
+ if (!g_main_context_is_owner(g_main_context_default()))
+ gdk_threads_leave();
+}
+
+#ifdef G_OS_WIN32
+
+static BOOL CALLBACK
+enumerate_vlc_windows_cb(HWND hWndvlc, LPARAM lParam)
+{
+ EnableWindow(hWndvlc, FALSE);
+ *(gboolean *)lParam = FALSE;
+
+ return TRUE;
+}
+
+/**
+ * @brief Callback for polling the availability of a libVLC event window.
+ *
+ * If found, it is disabled and the polling stops. This results in mouse click
+ * events being delivered to the GtkDrawingArea widget.
+ *
+ * @param data User data (\e GtkVlcPlayer widget)
+ * @return \c TRUE to continue polling, \c FALSE to stop
+ */
+static gboolean
+poll_vlc_event_window_cb(gpointer data)
+{
+ GtkWidget *drawing_area = gtk_bin_get_child(GTK_BIN(data));
+ GdkWindow *window = gtk_widget_get_window(drawing_area);
+
+ gboolean ret = TRUE;
+
+ EnumChildWindows(GDK_WINDOW_HWND(window),
+ enumerate_vlc_windows_cb, (LPARAM)&ret);
+
+ return ret;
+}
+
+static void
+widget_on_realize(GtkWidget *widget, gpointer user_data)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(user_data);
+ GdkWindow *window = gtk_widget_get_window(widget);
+
+ libvlc_media_player_set_hwnd(player->priv->media_player,
+ GDK_WINDOW_HWND(window));
+}
+
+#else
+
+static void
+widget_on_realize(GtkWidget *widget, gpointer user_data)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(user_data);
+ GdkWindow *window = gtk_widget_get_window(widget);
+
+ libvlc_media_player_set_xwindow(player->priv->media_player,
+ GDK_WINDOW_XID(window));
+}
+
+#endif
+
+static gboolean
+widget_on_click(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(user_data);
+ GtkWidget *fullscreen_window = player->priv->fullscreen_window;
+
+ if (fullscreen_window == NULL)
+ return TRUE;
+
+ if (event->type != GDK_2BUTTON_PRESS || event->button != 1)
+ return TRUE;
+
+ if (player->priv->isFullscreen) {
+ gtk_widget_reparent(widget, GTK_WIDGET(player));
+ gtk_widget_show(widget);
+ gtk_window_unfullscreen(GTK_WINDOW(fullscreen_window));
+ gtk_widget_hide(fullscreen_window);
+
+ player->priv->isFullscreen = FALSE;
+ } else {
+ set_transient_toplevel_window(GTK_WINDOW(fullscreen_window),
+ GTK_WIDGET(player));
+
+ gtk_window_fullscreen(GTK_WINDOW(fullscreen_window));
+ gtk_widget_show(fullscreen_window);
+ gtk_widget_reparent(widget, fullscreen_window);
+ gtk_widget_show(widget);
+
+ player->priv->isFullscreen = TRUE;
+ }
+
+ return TRUE;
+}
+
+static void
+time_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data)
+{
+ gtk_vlc_player_seek(GTK_VLC_PLAYER(user_data),
+ (gint64)gtk_adjustment_get_value(adj));
+}
+
+static void
+time_adj_on_changed(GtkAdjustment *adj, gpointer user_data)
+{
+ GtkVlcPlayer *player = GTK_VLC_PLAYER(user_data);
+
+ /* avoid signal handler recursion */
+ g_signal_handler_block(G_OBJECT(adj),
+ player->priv->time_adj_on_changed_id);
+ gtk_adjustment_set_upper(adj, (gdouble)gtk_vlc_player_get_length(player) +
+ gtk_adjustment_get_page_size(adj));
+ g_signal_handler_unblock(G_OBJECT(adj),
+ player->priv->time_adj_on_changed_id);
+}
+
+static void
+vol_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data)
+{
+ gtk_vlc_player_set_volume(GTK_VLC_PLAYER(user_data),
+ gtk_adjustment_get_value(adj));
+}
+
+static inline void
+set_transient_toplevel_window(GtkWindow *target, GtkWidget *widget)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
+
+ if (gtk_widget_is_toplevel(toplevel) && GTK_IS_WINDOW(toplevel))
+ gtk_window_set_transient_for(target, GTK_WINDOW(toplevel));
+}
+
+static void
+update_time(GtkVlcPlayer *player, gint64 new_time)
+{
+ g_signal_emit(player, gtk_vlc_player_signals[TIME_CHANGED_SIGNAL], 0,
+ new_time);
+
+ if (player->priv->time_adjustment != NULL) {
+ /* ensure that time_adj_on_value_changed() will not be executed */
+ g_signal_handler_block(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_value_changed_id);
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(player->priv->time_adjustment),
+ (gdouble)new_time);
+ g_signal_handler_unblock(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_value_changed_id);
+ }
+}
+
+static void
+update_length(GtkVlcPlayer *player, gint64 new_length)
+{
+ g_signal_emit(player, gtk_vlc_player_signals[LENGTH_CHANGED_SIGNAL], 0,
+ new_length);
+
+ if (player->priv->time_adjustment != NULL) {
+ GtkAdjustment *adj = GTK_ADJUSTMENT(player->priv->time_adjustment);
+
+ /* ensure that time_adj_on_changed() will not be executed */
+ g_signal_handler_block(G_OBJECT(adj),
+ player->priv->time_adj_on_changed_id);
+ gtk_adjustment_set_upper(adj, (gdouble)new_length +
+ gtk_adjustment_get_page_size(adj));
+ g_signal_handler_unblock(G_OBJECT(adj),
+ player->priv->time_adj_on_changed_id);
+ }
+}
+
+static void
+vlc_time_changed(const struct libvlc_event_t *event, void *user_data)
+{
+ assert(event->type == libvlc_MediaPlayerTimeChanged);
+
+ /* VLC callbacks may be invoked from another thread! */
+ maybe_lock_gdk();
+ update_time(GTK_VLC_PLAYER(user_data),
+ (gint64)event->u.media_player_time_changed.new_time);
+ maybe_unlock_gdk();
+}
+
+static void
+vlc_length_changed(const struct libvlc_event_t *event, void *user_data)
+{
+ assert(event->type == libvlc_MediaPlayerLengthChanged);
+
+ /* VLC callbacks may be invoked from another thread! */
+ maybe_lock_gdk();
+ update_length(GTK_VLC_PLAYER(user_data),
+ (gint64)event->u.media_player_length_changed.new_length);
+ maybe_unlock_gdk();
+}
+
+static void
+vlc_player_load_media(GtkVlcPlayer *player, libvlc_media_t *media)
+{
+ libvlc_media_parse(media);
+ libvlc_media_player_set_media(player->priv->media_player, media);
+
+ /* NOTE: media was parsed so get_duration works */
+ update_length(player, (gint64)libvlc_media_get_duration(media));
+ update_time(player, 0);
+}
+
+/*
+ * API
+ */
+
+/**
+ * @brief Construct new \e GtkVlcPlayer widget instance.
+ *
+ * @return New \e GtkVlcPlayer widget instance
+ */
+GtkWidget *
+gtk_vlc_player_new(void)
+{
+ return GTK_WIDGET(g_object_new(GTK_TYPE_VLC_PLAYER, NULL));
+}
+
+/**
+ * @brief Load media with specified filename into player widget
+ *
+ * It does not start playing until playback is started or toggled.
+ * "time-changed" and "length-changed" signals will be emitted immediately
+ * after successfully loading the media. The time-adjustment will also be
+ * reconfigured appropriately.
+ *
+ * @param player \e GtkVlcPlayer instance to load file into.
+ * @param file \e Filename to load
+ * @return \c TRUE on success, else \c FALSE
+ */
+gboolean
+gtk_vlc_player_load_filename(GtkVlcPlayer *player, const gchar *file)
+{
+ libvlc_media_t *media;
+
+ media = libvlc_media_new_path(player->priv->vlc_inst,
+ (const char *)file);
+ if (media == NULL)
+ return FALSE;
+ vlc_player_load_media(player, media);
+ libvlc_media_release(media);
+
+ return TRUE;
+}
+
+/**
+ * @brief Load media with specified URI into player widget
+ *
+ * It is otherwise identical to \ref gtk_vlc_player_load_filename.
+ *
+ * @sa gtk_vlc_player_load_filename
+ *
+ * @param player \e GtkVlcPlayer instance to load media into.
+ * @param uri \e URI to load
+ * @return \c TRUE on success, else \c FALSE
+ */
+gboolean
+gtk_vlc_player_load_uri(GtkVlcPlayer *player, const gchar *uri)
+{
+ libvlc_media_t *media;
+
+ media = libvlc_media_new_location(player->priv->vlc_inst,
+ (const char *)uri);
+ if (media == NULL)
+ return FALSE;
+ vlc_player_load_media(player, media);
+ libvlc_media_release(media);
+
+ return TRUE;
+}
+
+/**
+ * @brief Play back media if playback is currently paused
+ *
+ * If it is already playing, do nothing.
+ * In playback mode, there will be constant "time-changed" signal emissions
+ * and the time-adjustment's value will be set accordingly.
+ *
+ * @param player \e GtkVlcPlayer instance
+ */
+void
+gtk_vlc_player_play(GtkVlcPlayer *player)
+{
+ if (libvlc_media_player_play(player->priv->media_player) < 0)
+ return;
+
+ /*
+ * Workaround to get mouse click events on the drawing area widget
+ * that provides the low-level window for libVLC.
+ * On Win32, libVLC creates an event window (in a different thread)
+ * after playback start that "swallows" all click events.
+ * So we have to poll for the availability of that window and disable
+ * it.
+ */
+#ifdef G_OS_WIN32
+ g_timeout_add(POLL_VLC_EVENT_WINDOW_INTERVAL,
+ poll_vlc_event_window_cb, player);
+#endif
+}
+
+/**
+ * @brief Pause media playback if it is currently playing
+ *
+ * If it is already paused, do nothing.
+ *
+ * @param player \e GtkVlcPlayer instance
+ */
+void
+gtk_vlc_player_pause(GtkVlcPlayer *player)
+{
+ libvlc_media_player_pause(player->priv->media_player);
+}
+
+/**
+ * @brief Toggle media playback
+ *
+ * If it is currently playing, pause playback. If it is currently paused,
+ * start playback.
+ *
+ * @sa gtk_vlc_player_play
+ * @sa gtk_vlc_player_pause
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @return \c TRUE if media is playing \b after the operation,
+ * \c FALSE if it is paused.
+ */
+gboolean
+gtk_vlc_player_toggle(GtkVlcPlayer *player)
+{
+ if (libvlc_media_player_is_playing(player->priv->media_player))
+ gtk_vlc_player_pause(player);
+ else
+ gtk_vlc_player_play(player);
+
+ return (gboolean)libvlc_media_player_is_playing(player->priv->media_player);
+}
+
+/**
+ * @brief Stop media playback
+ *
+ * A "time-changed" signal will be emitted and the time-adjustment will be
+ * reset appropriately.
+ *
+ * @param player \e GtkVlcPlayer instance
+ */
+void
+gtk_vlc_player_stop(GtkVlcPlayer *player)
+{
+ gtk_vlc_player_pause(player);
+ libvlc_media_player_stop(player->priv->media_player);
+
+ update_time(player, 0);
+}
+
+/**
+ * @brief Set point of time in playback
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @param time New position in media (milliseconds)
+ */
+void
+gtk_vlc_player_seek(GtkVlcPlayer *player, gint64 time)
+{
+ libvlc_media_player_set_time(player->priv->media_player,
+ (libvlc_time_t)time);
+}
+
+/**
+ * @brief Set audio volume of playback
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @param volume New volume of playback. It must be between 0.0 (0%) and 2.0 (200%)
+ * of the original audio volume.
+ */
+void
+gtk_vlc_player_set_volume(GtkVlcPlayer *player, gdouble volume)
+{
+ libvlc_audio_set_volume(player->priv->media_player, (int)(volume*100.));
+}
+
+/**
+ * @brief Get media length
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @return Length of media (in milliseconds)
+ */
+gint64
+gtk_vlc_player_get_length(GtkVlcPlayer *player)
+{
+ return (gint64)libvlc_media_player_get_length(player->priv->media_player);
+}
+
+/**
+ * @brief Get time-adjustment currently used by \e GtkVlcPlayer
+ *
+ * The time-adjustment is an alternative to signal-callbacks and using the API
+ * for synchronizing the \e GtkVlcPlayer widget's current playback position with
+ * another widget (e.g. a \e GtkScale).
+ * The adjustment's value is in milliseconds.
+ * The widget will initialize one on construction, so there \e should always be
+ * an adjustment to get.
+ *
+ * @sa gtk_vlc_player_seek
+ * @sa GtkVlcPlayerClass::time_changed GtkVlcPlayerClass::length_changed
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @return Currently used time-adjustment
+ */
+GtkAdjustment *
+gtk_vlc_player_get_time_adjustment(GtkVlcPlayer *player)
+{
+ return player->priv->time_adjustment != NULL
+ ? GTK_ADJUSTMENT(player->priv->time_adjustment)
+ : NULL;
+}
+
+/**
+ * @brief Change time-adjustment used by \e GtkVlcPlayer
+ *
+ * The old adjustment will be
+ * unreferenced (and possibly destroyed) and a reference to the new
+ * adjustment will be fetched.
+ *
+ * @sa gtk_vlc_player_get_time_adjustment
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @param adj New \e GtkAdjustment to use as time-adjustment.
+ */
+void
+gtk_vlc_player_set_time_adjustment(GtkVlcPlayer *player, GtkAdjustment *adj)
+{
+ if (player->priv->time_adjustment == NULL)
+ return;
+
+ g_signal_handler_disconnect(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_changed_id);
+ g_signal_handler_disconnect(G_OBJECT(player->priv->time_adjustment),
+ player->priv->time_adj_on_value_changed_id);
+
+ g_object_unref(player->priv->time_adjustment);
+ player->priv->time_adjustment = GTK_OBJECT(adj);
+ g_object_ref_sink(player->priv->time_adjustment);
+
+ /*
+ * NOTE: not setting value and upper properly, as well as setting the
+ * page-size might cause problems if adjustment is set after loading/playing
+ * a media or setting it on other widgets
+ */
+ gtk_adjustment_configure(GTK_ADJUSTMENT(player->priv->time_adjustment),
+ 0., 0., 0.,
+ GTK_VLC_PLAYER_TIME_ADJ_STEP,
+ GTK_VLC_PLAYER_TIME_ADJ_PAGE,
+ 0.);
+
+ player->priv->time_adj_on_value_changed_id =
+ g_signal_connect(G_OBJECT(player->priv->time_adjustment),
+ "value-changed",
+ G_CALLBACK(time_adj_on_value_changed), player);
+ player->priv->time_adj_on_changed_id =
+ g_signal_connect(G_OBJECT(player->priv->time_adjustment),
+ "changed",
+ G_CALLBACK(time_adj_on_changed), player);
+}
+
+/**
+ * @brief Get volume-adjustment currently used by \e GtkVlcPlayer
+ *
+ * The volume-adjustment is an alternative to signal-callbacks and using the
+ * API for synchronizing the \e GtkVlcPlayer widget's current playback volume
+ * with another widget (e.g. a \e GtkVolumeButton).
+ * The adjustment's value is a percentage of gain to apply to the original
+ * signal (0.0 is 0%, 2.0 is 200%).
+ * The widget will initialize a volume-adjustment on construction, so there will
+ * always be an adjustment to get.
+ * In contrast to the time-adjustment, the volume-adjustment's value will never
+ * be changed by the \e GtkVlcPlayer widget.
+ *
+ * @sa gtk_vlc_player_set_volume
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @return Currently used volume-adjustment
+ */
+GtkAdjustment *
+gtk_vlc_player_get_volume_adjustment(GtkVlcPlayer *player)
+{
+ return player->priv->volume_adjustment != NULL
+ ? GTK_ADJUSTMENT(player->priv->volume_adjustment)
+ : NULL;
+}
+
+/**
+ * @brief Change volume-adjustment used by \e GtkVlcPlayer
+ *
+ * The old adjustment will be unreferenced (and possibly destroyed) and a
+ * reference to the new adjustment will be fetched.
+ *
+ * @sa gtk_vlc_player_get_volume_adjustment
+ *
+ * @param player \e GtkVlcPlayer instance
+ * @param adj New \e GtkAdjustment to use as volume-adjustment.
+ */
+void
+gtk_vlc_player_set_volume_adjustment(GtkVlcPlayer *player, GtkAdjustment *adj)
+{
+ if (player->priv->volume_adjustment == NULL)
+ return;
+
+ g_signal_handler_disconnect(G_OBJECT(player->priv->volume_adjustment),
+ player->priv->vol_adj_on_value_changed_id);
+
+ g_object_unref(player->priv->volume_adjustment);
+ player->priv->volume_adjustment = GTK_OBJECT(adj);
+ g_object_ref_sink(player->priv->volume_adjustment);
+
+ gtk_adjustment_configure(GTK_ADJUSTMENT(player->priv->volume_adjustment),
+ 1., 0., 1.,
+ GTK_VLC_PLAYER_VOL_ADJ_STEP,
+ GTK_VLC_PLAYER_VOL_ADJ_PAGE,
+ 0.);
+
+ player->priv->vol_adj_on_value_changed_id =
+ g_signal_connect(G_OBJECT(player->priv->volume_adjustment),
+ "value-changed",
+ G_CALLBACK(vol_adj_on_value_changed), player);
+}
diff --git a/src/gtk-vlc-player.h b/src/gtk-vlc-player.h
new file mode 100644
index 0000000..20f9df9
--- /dev/null
+++ b/src/gtk-vlc-player.h
@@ -0,0 +1,117 @@
+/**
+ * @file
+ * Header file necessary to include when using the \e GtkVlcPlayer
+ * widget.
+ */
+
+/*
+ * Copyright (C) 2012-2013 Otto-von-Guericke-Universität Magdeburg
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_VLC_PLAYER_H
+#define __GTK_VLC_PLAYER_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_VLC_PLAYER \
+ (gtk_vlc_player_get_type())
+/**
+ * Cast instance pointer to \e GtkVlcPlayer
+ *
+ * @param obj Object to cast to \e GtkVlcPlayer
+ * @return \e obj casted to \e GtkVlcPlayer
+ */
+#define GTK_VLC_PLAYER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_VLC_PLAYER, GtkVlcPlayer))
+#define GTK_VLC_PLAYER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_VLC_PLAYER, GtkVlcPlayerClass))
+#define GTK_IS_VLC_PLAYER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_VLC_PLAYER))
+#define GTK_IS_VLC_PLAYER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_VLC_PLAYER))
+#define GTK_VLC_PLAYER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_VLC_PLAYER, GtkVlcPlayerClass))
+
+/** @private */
+typedef struct _GtkVlcPlayerPrivate GtkVlcPlayerPrivate;
+
+/**
+ * \e GtkVlcPlayer instance structure
+ */
+typedef struct _GtkVlcPlayer {
+ GtkAlignment parent_instance; /**< Parent instance structure */
+
+ GtkVlcPlayerPrivate *priv; /**< @private */
+} GtkVlcPlayer;
+
+/**
+ * \e GtkExperimentNavigator class structure
+ */
+typedef struct _GtkVlcPlayerClass {
+ GtkAlignmentClass parent_class; /**< Parent class structure */
+
+ /**
+ * Callback function to invoke when emitting the "time-changed"
+ * signal.
+ *
+ * @param self \e GtkVlcPlayer widget that emitted the signal
+ * @param new_time New (current) position of playback in milliseconds
+ */
+ void (*time_changed) (GtkVlcPlayer *self, gint64 new_time);
+
+ /**
+ * Callback function to invoke when emitting the "length-changed"
+ * signal.
+ *
+ * @param self \e GtkVlcPlayer widget that emitted the signal
+ * @param new_length New (current) length of media loaded into player (milliseconds)
+ */
+ void (*length_changed) (GtkVlcPlayer *self, gint64 new_length);
+} GtkVlcPlayerClass;
+
+/** @private */
+GType gtk_vlc_player_get_type(void);
+
+/*
+ * API
+ */
+GtkWidget *gtk_vlc_player_new(void);
+
+gboolean gtk_vlc_player_load_filename(GtkVlcPlayer *player, const gchar *file);
+gboolean gtk_vlc_player_load_uri(GtkVlcPlayer *player, const gchar *uri);
+
+void gtk_vlc_player_play(GtkVlcPlayer *player);
+void gtk_vlc_player_pause(GtkVlcPlayer *player);
+gboolean gtk_vlc_player_toggle(GtkVlcPlayer *player);
+void gtk_vlc_player_stop(GtkVlcPlayer *player);
+
+void gtk_vlc_player_seek(GtkVlcPlayer *player, gint64 time);
+void gtk_vlc_player_set_volume(GtkVlcPlayer *player, gdouble volume);
+
+gint64 gtk_vlc_player_get_length(GtkVlcPlayer *player);
+
+GtkAdjustment *gtk_vlc_player_get_time_adjustment(GtkVlcPlayer *player);
+void gtk_vlc_player_set_time_adjustment(GtkVlcPlayer *player, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_vlc_player_get_volume_adjustment(GtkVlcPlayer *player);
+void gtk_vlc_player_set_volume_adjustment(GtkVlcPlayer *player, GtkAdjustment *adj);
+
+G_END_DECLS
+
+#endif