From 91c99c62d926e765654ff0ded57078460ba2c41b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Svoboda?= <ondrej@svobodasoft.cz>
Date: Wed, 9 Jul 2014 23:18:38 +0200
Subject: [PATCH 2/2] Implement partial MPRIS 1.0 support.

Forum thread: http://moc.daper.net/node/496
---
 audio.c               |  10 +-
 audio.h               |   2 +-
 configure.in          |  16 ++
 main.c                |   3 +
 mpris.c               | 553 ++++++++++++++++++++++++++++++++++++++++++
 mpris.h               |  25 ++
 mpris_introspection.h | 111 +++++++++
 out_buf.c             |   6 +-
 out_buf.h             |   2 +-
 server.c              |  35 ++-
 server.h              |   1 +
 11 files changed, 751 insertions(+), 13 deletions(-)
 create mode 100644 mpris.c
 create mode 100644 mpris.h
 create mode 100644 mpris_introspection.h

diff --git a/audio.c b/audio.c
index 69f6067..1d46cab 100644
--- a/audio.c
+++ b/audio.c
@@ -64,7 +64,7 @@ static pthread_t playing_thread = 0;  /* tid of play thread */
 static int play_thread_running = 0;

 /* currently played file */
-static int curr_playing = -1;
+int curr_playing = -1;
 /* file we played before playing songs from queue */
 static char *before_queue_fname = NULL;
 static char *curr_playing_fname = NULL;
@@ -72,7 +72,7 @@ static char *curr_playing_fname = NULL;
  * so we know that when the queue is empty, we should play the regular
  * playlist from the beginning. */
 static int started_playing_in_queue = 0;
-static pthread_mutex_t curr_playing_mut = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t curr_playing_mut = PTHREAD_MUTEX_INITIALIZER;

 static struct out_buf out_buf;
 static struct hw_funcs hw;
@@ -93,8 +93,8 @@ static pthread_mutex_t request_mut = PTHREAD_MUTEX_INITIALIZER;
 static struct plist playlist;
 static struct plist shuffled_plist;
 static struct plist queue;
-static struct plist *curr_plist; /* currently used playlist */
-static pthread_mutex_t plist_mut = PTHREAD_MUTEX_INITIALIZER;
+struct plist *curr_plist; /* currently used playlist */
+pthread_mutex_t plist_mut = PTHREAD_MUTEX_INITIALIZER;

 /* Is the audio device opened? */
 static int audio_opened = 0;
@@ -851,7 +851,7 @@ int audio_send_pcm (const char *buf, const size_t size)
 }

 /* Get current time of the song in seconds. */
-int audio_get_time ()
+float audio_get_time ()
 {
 	return state != STATE_STOP ? out_buf_time_get (&out_buf) : 0;
 }
diff --git a/audio.h b/audio.h
index c593eb3..d355c44 100644
--- a/audio.h
+++ b/audio.h
@@ -239,7 +239,7 @@ int audio_get_bpf ();
 int audio_get_bps ();
 int audio_get_buf_fill ();
 void audio_close ();
-int audio_get_time ();
+float audio_get_time ();
 int audio_get_state ();
 int audio_get_prev_state ();
 void audio_plist_add (const char *file);
diff --git a/configure.in b/configure.in
index e03b0c1..3a2f707 100644
--- a/configure.in
+++ b/configure.in
@@ -439,6 +439,21 @@ fi
 dnl Check for XZ.
 AC_CHECK_PROG([XZ_MISSING], [xz], [no], [yes])

+dnl mpris
+AC_ARG_WITH(mpris, AS_HELP_STRING(--without-mpris, Compile without MPRIS/D-Bus support.))
+if test "x$with_mpris" != "xno"
+then
+       PKG_CHECK_MODULES(DBus, [dbus-1 >= 1.2],
+            [COMPILE_MPRIS="yes"
+             AC_DEFINE([HAVE_MPRIS], 1, [Define if you want to have MPRIS/D-Bus support.])
+             EXTRA_LIBS="$EXTRA_LIBS -ldbus-1"
+             EXTRA_OBJS="$EXTRA_OBJS mpris.o"
+             CFLAGS="$CFLAGS `pkg-config dbus-1 --cflags`"],
+            [COMPILE_MPRIS="no"])
+else
+       COMPILE_MPRIS="no"
+fi
+
 AC_SUBST(EXTRA_LIBS)
 AC_SUBST(EXTRA_DISTS)

@@ -467,6 +482,7 @@ echo "RCC:               "$COMPILE_RCC
 echo "Network streams:   "$COMPILE_CURL
 echo "Resampling:        "$COMPILE_SAMPLERATE
 echo "MIME magic:        "$COMPILE_MAGIC
+echo "MPRIS/D-Bus:       "$COMPILE_MPRIS
 echo "-----------------------------------------------------------------------"
 echo

diff --git a/main.c b/main.c
index 32d66c8..ae9ef4c 100644
--- a/main.c
+++ b/main.c
@@ -275,6 +275,9 @@ static void show_version ()
 #endif
 #ifdef HAVE_SAMPLERATE
 	printf (" resample");
+#endif
+#ifdef HAVE_MPRIS
+	printf (" MPRIS/D-Bus");
 #endif
 	putchar ('\n');

diff --git a/mpris.c b/mpris.c
new file mode 100644
index 0000000..c94afb6
--- /dev/null
+++ b/mpris.c
@@ -0,0 +1,553 @@
+/*
+ * MOC - music on console
+ * Copyright (C) 2009-2014 Ondřej Svoboda <ondrej@svobodasoft.cz>
+ *
+ * MPRIS (Media Player Remote Interfacing Specification) 1.0 implementation.
+ * This is a D-Bus interface to MOC.
+ * All processing is done in a separate server thread.
+ *
+ * TODO:
+ * Almost no error-checking during message processing will be coded until it is
+ * declared necessary. Errors are said to only emerge when there is
+ * not enough memory. In that case the thread will stop looping anyway (I hope).
+ *
+ * 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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include "audio.h"
+#include "common.h"
+#include "files.h"
+#include "log.h"
+#include "mpris.h"
+#include "mpris_introspection.h"
+#include "options.h"
+#include "player.h"
+#include "playlist.h"
+#include "protocol.h"
+#include "server.h"
+#include "tags_cache.h"
+
+static DBusConnection *dbus_conn; /* Connection handle. */
+static DBusError dbus_err;        /* Error flag. */
+static DBusMessage *msg;          /* An incoming message. */
+static DBusMessageIter args_in, args_out;    /* Iterators for in- or outcoming messages' content. */
+static DBusMessageIter array, dict, variant; /* Iterators for containers only used in outgoing messages. */
+
+/* Shared with audio.c */
+/* Track number in the current playlist.
+ * It is not what we want when the playlist is shuffled.
+ * TODO: Get the right track number. */
+extern int curr_playing;
+extern struct plist *curr_plist;
+extern pthread_mutex_t curr_playing_mut;
+extern pthread_mutex_t plist_mut;
+
+/* Shared with server.c */
+/* TODO: Do we need mutex for these? */
+extern int server_quit;
+extern struct tags_cache tags_cache;
+
+/* Flags to be changed by the server + their mutex. */
+static int mpris_track_changed = 0;
+static int mpris_status_changed = 0;
+static int mpris_caps_changed = 0;
+static int mpris_tracklist_changed = 0;
+static pthread_mutex_t mpris_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Connection to D-Bus happens here. If it fails, for any reason, we just move
+ * on as the MPRIS/D-Bus feature is not crucial for the server.
+ * Later, it could be possible to rerun the initialization and the thread
+ * on-demand or automatically after a period of time. */
+void mpris_init()
+{
+	dbus_error_init(&dbus_err);
+
+	dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err);
+	if (dbus_error_is_set(&dbus_err)) {
+		logit("Error while connecting to D-Bus: %s", dbus_err.message);
+		dbus_error_free(&dbus_err);
+		return;
+	}
+
+	if (dbus_conn == NULL) {
+		logit("Connection to D-Bus not established.");
+		return;
+	}
+
+	int ret = dbus_bus_request_name(dbus_conn, MPRIS_BUS_NAME,
+	DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_err);
+	if (dbus_error_is_set(&dbus_err)) {
+		logit("Error while requesting a bus name: %s", dbus_err.message);
+		dbus_error_free(&dbus_err);
+		return;
+	}
+
+	if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+		logit("Could not become the primary owner of the bus name.");
+		return;
+	}
+
+	logit("Everything went fine when connecting to D-Bus.");
+}
+
+/* Low-level wrappers for sending message arguments with possible error checking. */
+
+static void msg_add_string(char **value)
+{
+	if (!dbus_message_iter_append_basic(&args_out, DBUS_TYPE_STRING, value)) {
+		logit("Out of memory!");
+		/* Error handling would go here. */
+	}
+}
+
+static void msg_add_int32(int *value)
+{
+	dbus_message_iter_append_basic(&args_out, DBUS_TYPE_INT32, value);
+}
+
+static void msg_add_uint16(unsigned short int *value)
+{
+	dbus_message_iter_append_basic(&args_out, DBUS_TYPE_UINT16, value);
+}
+
+/* Helper functions for sending bigger chunks of data or with some added logic. */
+
+static void mpris_send_tracklist_length()
+{
+	LOCK(plist_mut);
+	int length = curr_plist != NULL ? plist_count(curr_plist) : 0;
+	UNLOCK(plist_mut);
+	msg_add_int32(&length);
+}
+
+static void mpris_send_metadata_string_field(char **key, char **value)
+{
+	dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, 0, &dict);
+		dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, key);
+		dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &variant);
+			dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, value);
+		dbus_message_iter_close_container(&dict, &variant);
+	dbus_message_iter_close_container(&array, &dict);
+}
+
+/* TODO: If tags are missing, at least a title made from file name should be returned! */
+static void mpris_send_metadata(int item)
+{
+	char *location_key = "location";
+	char *title_key = "title";
+	char *artist_key = "artist";
+	char *location_val = NULL;
+	char *file = NULL;
+	struct file_tags *tags;
+
+	if (item >= 0) {
+		LOCK(plist_mut);
+		file = plist_get_file(curr_plist, item);
+		UNLOCK(plist_mut);
+	}
+	if (file == NULL) {
+		file = xstrdup("");
+		tags = tags_new();
+	} else {
+		tags = tags_cache_get_immediate(&tags_cache, file, TAGS_COMMENTS | TAGS_TIME);
+	}
+
+	if (!is_url(file)) {
+		location_val = (char *)xmalloc(sizeof(char) * (7 + strlen(file) + 1));
+		strcpy(location_val, "file://");
+		strcat(location_val, file);
+	} else {
+		location_val = xstrdup(file);
+	}
+	free(file);
+
+	dbus_message_iter_open_container(&args_out, DBUS_TYPE_ARRAY, "{sv}", &array);
+	mpris_send_metadata_string_field(&location_key, &location_val);
+	if (tags->title != NULL)
+		mpris_send_metadata_string_field(&title_key, &tags->title);
+	if (tags->artist != NULL)
+		mpris_send_metadata_string_field(&artist_key, &tags->artist);
+
+	dbus_message_iter_close_container(&args_out, &array);
+
+	free(location_val);
+	tags_free(tags);
+}
+
+static void mpris_send_status()
+{
+	DBusMessageIter structure;
+	int state, shuffle, repeat_current, next, repeat;
+
+	switch (audio_get_state()) {
+		case STATE_PLAY:
+			state = 0;
+			break;
+		case STATE_PAUSE:
+			state = 1;
+			break;
+		case STATE_STOP:
+			state = 2;
+	}
+
+	shuffle = options_get_int("Shuffle");
+	repeat  = options_get_int("Repeat");
+	next	= options_get_int("AutoNext");
+	repeat_current = !next && repeat;
+
+	/* I saw a nice usage of a C++ << operator for appending values to the message. */
+	dbus_message_iter_open_container(&args_out, DBUS_TYPE_STRUCT, 0, &structure);
+		dbus_message_iter_append_basic(&structure, DBUS_TYPE_INT32, &state);
+		dbus_message_iter_append_basic(&structure, DBUS_TYPE_INT32, &shuffle);
+		dbus_message_iter_append_basic(&structure, DBUS_TYPE_INT32, &repeat_current);
+		dbus_message_iter_append_basic(&structure, DBUS_TYPE_INT32, &repeat);
+	dbus_message_iter_close_container(&args_out, &structure);
+}
+
+/* TODO: stub */
+static void mpris_send_caps()
+{
+	int caps = 0
+	| CAN_HAS_TRACKLIST			/* Yes, MOC always has a playlist, be it an actual playlist or a directory. */
+	;
+
+	msg_add_int32(&caps);
+}
+
+/* Functions for sending signals. */
+
+static void mpris_tracklist_change_signal()
+{
+	msg = dbus_message_new_signal("/TrackList", MPRIS_IFACE, "TrackListChange");
+	dbus_message_iter_init_append(msg, &args_out);
+
+	mpris_send_tracklist_length();
+
+	dbus_connection_send(dbus_conn, msg, NULL);
+	dbus_connection_flush(dbus_conn);
+
+	dbus_message_unref(msg);
+}
+
+static void mpris_track_change_signal()
+{
+	msg = dbus_message_new_signal("/Player", MPRIS_IFACE, "TrackChange");
+	dbus_message_iter_init_append(msg, &args_out);
+
+	LOCK(curr_playing_mut);
+	int curr = curr_playing;
+	UNLOCK(curr_playing_mut);
+	mpris_send_metadata(curr);
+
+	dbus_connection_send(dbus_conn, msg, NULL);
+	dbus_connection_flush(dbus_conn);
+
+	dbus_message_unref(msg);
+}
+
+static void mpris_status_change_signal()
+{
+	msg = dbus_message_new_signal("/Player", MPRIS_IFACE, "StatusChange");
+	dbus_message_iter_init_append(msg, &args_out);
+
+	mpris_send_status();
+
+	dbus_connection_send(dbus_conn, msg, NULL);
+	dbus_connection_flush(dbus_conn);
+
+	dbus_message_unref(msg);
+}
+
+static void mpris_caps_change_signal()
+{
+	msg = dbus_message_new_signal("/Player", MPRIS_IFACE, "CapsChange");
+	dbus_message_iter_init_append(msg, &args_out);
+
+	mpris_send_caps();
+
+	dbus_connection_send(dbus_conn, msg, NULL);
+	dbus_connection_flush(dbus_conn);
+
+	dbus_message_unref(msg);
+}
+
+/* Argument checking for incoming messages. */
+
+static int mpris_arg_bool()
+{
+	return dbus_message_iter_init(msg, &args_in) &&
+	       dbus_message_iter_get_arg_type(&args_in) == DBUS_TYPE_BOOLEAN;
+}
+
+static int mpris_arg_int32()
+{
+	return dbus_message_iter_init(msg, &args_in) &&
+	       dbus_message_iter_get_arg_type(&args_in) == DBUS_TYPE_INT32;
+}
+
+/* Implementation of D-Bus methods (incomplete, see comments). */
+
+static void mpris_root_methods()
+{
+	if (dbus_message_is_method_call(msg, INTROSPECTION_IFACE, "Introspect")) {
+		msg_add_string(&root_introspection);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Quit")) {
+		server_quit = 1; /* TODO: Why care about using mutex? */
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Identity")) {
+		char *identity = xstrdup(PACKAGE_STRING);
+		msg_add_string(&identity);
+		free(identity);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "MprisVersion")) {
+		short unsigned int version_major = 1, version_minor = 0;
+		msg_add_uint16(&version_major);
+		msg_add_uint16(&version_minor);
+	}
+}
+
+static void mpris_tracklist_methods()
+{
+	if (dbus_message_is_method_call(msg, INTROSPECTION_IFACE, "Introspect")) {
+		msg_add_string(&tracklist_introspection);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetMetadata")) {
+		if (mpris_arg_int32()) {
+			int item;
+			dbus_message_iter_get_basic(&args_in, &item);
+			mpris_send_metadata(item);
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetCurrentTrack")) {
+		/* In curr_playing there's not a number we always want. */
+		LOCK(curr_playing_mut);
+		int curr = curr_playing != -1 ? curr_playing : 0;
+		UNLOCK(curr_playing_mut);
+		msg_add_int32(&curr);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetLength")) {
+		mpris_send_tracklist_length();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "AddTrack")) {
+		/*
+		 * Not implemented yet!
+		 * There are some issues to handle:
+		 * - every client can have its own playlist, which one to add a file to then?
+		 * - to add a file we seem to need to duplicate a few functions, such as
+		 *   add_file_plist(), play_from_url(), add_url_to_plist()
+		 *   from interface.c and code chunks processing the CMD_CLI_PLIST_ADD command
+		 *   in server.c
+		 */
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "DelTrack")) {
+		/* Not implemented yet for the same reason as for AddTrack. */
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "SetLoop")) {
+		if (mpris_arg_bool()) {
+			int loop_plist;
+			dbus_message_iter_get_basic(&args_in, &loop_plist);
+			options_set_int("Repeat", loop_plist);
+			/* We are asked to loop the playlist, so make it play more than one file. */
+			if (loop_plist) options_set_int("AutoNext", 1);
+			add_event_all(EV_OPTIONS, NULL);
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "SetRandom")) {
+		if (mpris_arg_bool()) {
+			int random;
+			dbus_message_iter_get_basic(&args_in, &random);
+			options_set_int("Shuffle", random);
+			add_event_all(EV_OPTIONS, NULL);
+		}
+	}
+}
+
+static void mpris_player_methods()
+{
+	if (dbus_message_is_method_call(msg, INTROSPECTION_IFACE, "Introspect")) {
+		msg_add_string(&player_introspection);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Next")) {
+		audio_next();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Prev")) {
+		audio_prev();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Pause")) {
+		switch (audio_get_state()) {
+			case STATE_PAUSE:
+				audio_unpause();
+				break;
+			case STATE_PLAY:
+				audio_pause();
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Stop")) {
+		audio_stop();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Play")) {
+		/* TODO: This does not make MOC play a song if it hasn't played one yet! */
+		char *file;
+		switch (audio_get_state()) {
+			case STATE_PAUSE:
+				audio_unpause();
+				break;
+			case STATE_PLAY:
+				LOCK(plist_mut);
+				LOCK(curr_playing_mut);
+				if (curr_plist != NULL && curr_playing != -1) {
+					file = plist_get_file (curr_plist, curr_playing);
+					UNLOCK(plist_mut);
+					UNLOCK(curr_playing_mut);
+					audio_play(file);
+					free(file);
+				} else {
+					UNLOCK(plist_mut);
+					UNLOCK(curr_playing_mut);
+				}
+				break;
+			case STATE_STOP:
+				audio_play("");
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "Repeat")) {
+		if (mpris_arg_bool()) {
+			int repeat_current;
+			dbus_message_iter_get_basic(&args_in, &repeat_current);
+			options_set_int("Repeat", repeat_current);
+			options_set_int("AutoNext", !repeat_current);
+			add_event_all(EV_OPTIONS, NULL);
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetStatus")) {
+		mpris_send_status();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetMetadata")) {
+		LOCK(curr_playing_mut);
+		int curr = curr_playing;
+		UNLOCK(curr_playing_mut);
+		mpris_send_metadata(curr);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "GetCaps")) {
+		mpris_send_caps();
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "VolumeSet")) {
+		if (mpris_arg_int32()) {
+			int volume;
+			dbus_message_iter_get_basic(&args_in, &volume);
+			/* TODO: Check the parameter first! */
+			audio_set_mixer(volume);
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "VolumeGet")) {
+		int volume = audio_get_mixer();
+		msg_add_int32(&volume);
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "PositionSet")) {
+		/* TODO: Should support milisecond precision too */
+		if (mpris_arg_int32()) {
+			int pos;
+			dbus_message_iter_get_basic(&args_in, &pos);
+			/* TODO: Do we need to check the value for correctness? */
+			audio_jump_to(pos / 1000);
+		}
+	} else if (dbus_message_is_method_call(msg, MPRIS_IFACE, "PositionGet")) {
+		int pos = audio_get_time() * 1000;
+		msg_add_int32(&pos);
+	}
+}
+
+/* A server thread where all D-Bus messages are received and signals are sent. */
+void *mpris_thread(void *unused ATTR_UNUSED)
+{
+	DBusMessage *reply;
+	const char *path; /* The path an incoming message has been sent to. */
+
+	/* If no D-Bus connection has been established we have nothing to do. */
+	if (dbus_conn == NULL) return NULL;
+
+	logit("Starting the MPRIS thread.");
+
+	/* Wait for an incoming message for a max. of MPRIS_TIMEOUT (50 ms). */
+	while (dbus_connection_read_write(dbus_conn, MPRIS_TIMEOUT)) {
+		if (server_quit) {
+			logit("Stopping the MPRIS thread due to server exit.");
+			return NULL;
+		}
+
+		/* Send signals if necessary. */
+		LOCK(mpris_mutex);
+		if (mpris_tracklist_changed) {
+			mpris_tracklist_change_signal();
+			mpris_tracklist_changed = 0;
+		}
+		if (mpris_track_changed) {
+			mpris_track_change_signal();
+			mpris_track_changed = 0;
+		}
+		if (mpris_caps_changed) {
+			mpris_caps_change_signal();
+			mpris_caps_changed = 0;
+		}
+		if (mpris_status_changed) {
+			mpris_status_change_signal();
+			mpris_status_changed = 0;
+		}
+		UNLOCK(mpris_mutex);
+
+		/* Fetch an incoming message. */
+		msg = dbus_connection_pop_message(dbus_conn);
+		if (msg == NULL) continue;
+
+		/* Respond to all messages. They say in the specification some messages
+		 * are sometimes no-ops, so this is the place to start moving code. */
+
+		reply = dbus_message_new_method_return(msg);
+		dbus_message_iter_init_append(reply, &args_out);
+
+		/* Process the incoming message and prepare a response. */
+		if ((path = dbus_message_get_path(msg)) != NULL) {
+			if (!strcmp("/", path))
+				mpris_root_methods();
+			else if (!strcmp("/Player", path))
+				mpris_player_methods();
+			else if (!strcmp("/TrackList", path))
+				mpris_tracklist_methods();
+		}
+
+		/* Send the response and clean up. */
+		dbus_connection_send(dbus_conn, reply, NULL);
+		dbus_message_unref(msg);
+		dbus_connection_flush(dbus_conn);
+	}
+
+	logit("Stopping MPRIS thread due to a loss of communication with D-Bus.");
+	return NULL;
+}
+
+/* Hooks in the core/server. */
+
+/* Not used yet. */
+void mpris_tracklist_change()
+{
+	LOCK(mpris_mutex);
+	mpris_tracklist_changed = 1;
+	UNLOCK(mpris_mutex);
+}
+
+void mpris_track_change()
+{
+	LOCK(mpris_mutex);
+	mpris_track_changed = 1;
+	UNLOCK(mpris_mutex);
+}
+
+void mpris_status_change()
+{
+	LOCK(mpris_mutex);
+	mpris_status_changed = 1;
+	UNLOCK(mpris_mutex);
+}
+
+/* Not used yet. */
+void mpris_caps_change()
+{
+	LOCK(mpris_mutex);
+	mpris_caps_changed = 1;
+	UNLOCK(mpris_mutex);
+}
+
+inline void mpris_exit()
+{
+	pthread_mutex_destroy(&mpris_mutex);
+}
diff --git a/mpris.h b/mpris.h
new file mode 100644
index 0000000..20806fb
--- /dev/null
+++ b/mpris.h
@@ -0,0 +1,25 @@
+#ifndef MPRIS_H
+#define MPRIS_H
+
+#define MPRIS_TIMEOUT			50
+#define MPRIS_BUS_NAME			"org.mpris.moc"
+#define MPRIS_IFACE				"org.freedesktop.MediaPlayer"
+#define INTROSPECTION_IFACE		"org.freedesktop.DBus.Introspectable"
+
+#define CAN_GO_NEXT				1 << 0
+#define CAN_GO_PREV				1 << 1
+#define CAN_PAUSE				1 << 2
+#define CAN_PLAY				1 << 3
+#define CAN_SEEK				1 << 4
+#define CAN_PROVIDE_METADATA	1 << 5
+#define CAN_HAS_TRACKLIST		1 << 6
+
+void mpris_init();
+void *mpris_thread(void *unused ATTR_UNUSED);
+inline void mpris_exit();
+void mpris_track_change();
+void mpris_status_change();
+void mpris_caps_change();
+void mpris_tracklist_change();
+
+#endif
diff --git a/mpris_introspection.h b/mpris_introspection.h
new file mode 100644
index 0000000..95a4f2b
--- /dev/null
+++ b/mpris_introspection.h
@@ -0,0 +1,111 @@
+#ifndef MPRIS_INTROSPECTION_H
+#define MPRIS_INTROSPECTION_H
+
+static char *root_introspection =
+"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'\n"
+"	'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n"
+"<node name='/org/mpris/moc'>\n"
+"	<interface name='org.freedesktop.MediaPlayer'>\n"
+"		<method name='Identity'>\n"
+"			<arg type='s' direction='out'/>\n"
+"		</method>\n"
+"		<method name='Quit'>\n"
+"		</method>\n"
+"		<method name='MprisVersion'>\n"
+"			<arg type='(qq)' direction='out'/>\n"
+"		</method>\n"
+"	</interface>\n"
+"	<node name='TrackList'/>\n"
+"	<node name='Player'/>\n"
+"</node>"
+;
+
+static char *tracklist_introspection =
+"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'\n"
+"	'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n"
+"<node name='/org/mpris/moc/TrackList'>\n"
+"	<interface name='org.freedesktop.MediaPlayer'>\n"
+"		<method name='GetMetadata'>\n"
+"			<arg type='i' direction='in'/>\n"
+"			<arg type='a{sv}' direction='out'/>\n"
+"		</method>\n"
+"		<method name='GetCurrentTrack'>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<method name='GetLength'>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<method name='AddTrack'>\n"
+"			<arg type='s' direction='in'/>\n"
+"			<arg type='b' direction='in'/>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<method name='DelTrack'>\n"
+"			<arg type='i'/>\n"
+"		</method>\n"
+"		<method name='SetLoop'>\n"
+"			<arg type='b'/>\n"
+"		</method>\n"
+"		<method name='SetRandom'>\n"
+"			<arg type='b'/>\n"
+"		</method>\n"
+"		<signal name='TrackListChange'>\n"
+"			<arg type='i'/>\n"
+"		</signal>\n"
+"	</interface>\n"
+"</node>"
+;
+
+static char *player_introspection =
+"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'\n"
+"	'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n"
+"<node name='/org/mpris/moc/Player'>\n"
+"	<interface name='org.freedesktop.MediaPlayer'>\n"
+"		<method name='Next'>\n"
+"		</method>\n"
+"		<method name='Prev'>\n"
+"		</method>\n"
+"		<method name='Pause'>\n"
+"		</method>\n"
+"		<method name='Stop'>\n"
+"		</method>\n"
+"		<method name='Play'>\n"
+"		</method>\n"
+"		<method name='Repeat'>\n"
+"			<arg type='b'/>\n"
+"		</method>\n"
+"		<method name='GetStatus'>\n"
+"			<arg type='(iiii)' direction='out'/>\n"
+"		</method>\n"
+"		<method name='GetMetadata'>\n"
+"			<arg type='a{sv}' direction='out'/>\n"
+"		</method>\n"
+"		<method name='GetCaps'>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<method name='VolumeSet'>\n"
+"			<arg type='i'/>\n"
+"		</method>\n"
+"		<method name='VolumeGet'>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<method name='PositionSet'>\n"
+"			<arg type='i'/>\n"
+"		</method>\n"
+"		<method name='PositionGet'>\n"
+"			<arg type='i' direction='out'/>\n"
+"		</method>\n"
+"		<signal name='TrackChange'>\n"
+"			<arg type='a{sv}'/>\n"
+"		</signal>\n"
+"		<signal name='StatusChange'>\n"
+"			<arg type='(iiii)'/>\n"
+"		</signal>\n"
+"		<signal name='CapsChange'>\n"
+"			<arg type='i'/>\n"
+"		</signal>\n"
+"	</interface>\n"
+"</node>"
+;
+
+#endif
diff --git a/out_buf.c b/out_buf.c
index 5b39d01..0e3cf1b 100644
--- a/out_buf.c
+++ b/out_buf.c
@@ -38,7 +38,7 @@

 /* Don't play more than this value (in seconds) in one audio_play().
  * This prevents locking. */
-#define AUDIO_MAX_PLAY		0.1
+#define AUDIO_MAX_PLAY		0.01
 #define AUDIO_MAX_PLAY_BYTES	32768

 #ifdef OUT_TEST
@@ -359,9 +359,9 @@ void out_buf_time_set (struct out_buf *buf, const float time)
  * previous audio then the value returned may be negative and it is
  * up to the caller to handle this appropriately in the context of
  * its own processing. */
-int out_buf_time_get (struct out_buf *buf)
+float out_buf_time_get (struct out_buf *buf)
 {
-	int time;
+	float time;
 	int bps = audio_get_bps ();

 	LOCK (buf->mutex);
diff --git a/out_buf.h b/out_buf.h
index 6a10d98..6e26816 100644
--- a/out_buf.h
+++ b/out_buf.h
@@ -46,7 +46,7 @@ void out_buf_unpause (struct out_buf *buf);
 void out_buf_stop (struct out_buf *buf);
 void out_buf_reset (struct out_buf *buf);
 void out_buf_time_set (struct out_buf *buf, const float time);
-int out_buf_time_get (struct out_buf *buf);
+float out_buf_time_get (struct out_buf *buf);
 void out_buf_set_free_callback (struct out_buf *buf,
 		out_buf_free_callback callback);
 int out_buf_get_free (struct out_buf *buf);
diff --git a/server.c b/server.c
index 24cb898..0f6c7dc 100644
--- a/server.c
+++ b/server.c
@@ -48,6 +48,9 @@
 #include "files.h"
 #include "softmixer.h"
 #include "equalizer.h"
+#ifdef HAVE_MPRIS
+# include "mpris.h"
+#endif

 #define SERVER_LOG	"mocp_server_log"
 #define PID_FILE	"pid"
@@ -69,11 +72,16 @@ static struct client clients[CLIENTS_MAX];
 /* Thread ID of the server thread. */
 static pthread_t server_tid;

+/* Thread ID of the MPRIS thread. */
+#ifdef HAVE_MPRIS
+static pthread_t mpris_tid;
+#endif
+
 /* Pipe used to wake up the server from select() from another thread. */
 static int wake_up_pipe[2];

 /* Set to 1 when a signal arrived causing the program to exit. */
-static volatile int server_quit = 0;
+volatile int server_quit = 0;

 /* Information about currently played file */
 static struct {
@@ -88,7 +96,7 @@ static struct {
 	-1
 };

-static struct tags_cache tags_cache;
+struct tags_cache tags_cache;

 extern char **environ;

@@ -378,6 +386,11 @@ int server_init (int debugging, int foreground)
 	tags_cache_init (&tags_cache, options_get_int("TagsCacheSize"));
 	tags_cache_load (&tags_cache, create_file_name("cache"));

+	#ifdef HAVE_MPRIS
+	mpris_init ();
+	pthread_create (&mpris_tid, NULL, mpris_thread, NULL);
+	#endif
+
 	server_tid = pthread_self ();
 	thread_signal (SIGTERM, sig_exit);
 	thread_signal (SIGINT, foreground ? sig_exit : SIG_IGN);
@@ -461,6 +474,10 @@ static void on_song_change ()
 		return;
 	}

+	#ifdef HAVE_MPRIS
+	mpris_track_change ();
+	#endif
+
 	curr_tags = tags_cache_get_immediate (&tags_cache, curr_file,
 	                                      TAGS_COMMENTS | TAGS_TIME);
 	arg_list = lists_strs_new (lists_strs_size (on_song_change));
@@ -585,7 +602,7 @@ static inline bool is_plist_event (const int event)
 	return result;
 }

-static void add_event_all (const int event, const void *data)
+void add_event_all (const int event, const void *data)
 {
 	int i;
 	int added = 0;
@@ -677,6 +694,9 @@ static void server_shutdown ()
 {
 	logit ("Server exiting...");
 	audio_exit ();
+	#ifdef HAVE_MPRIS
+	mpris_exit ();
+	#endif
 	tags_cache_save (&tags_cache, create_file_name("tags_cache"));
 	tags_cache_destroy (&tags_cache);
 	unlink (socket_name());
@@ -861,6 +881,9 @@ static int get_set_option (struct client *cli)
 	}

 	options_set_int (name, val);
+	#ifdef HAVE_MPRIS
+	mpris_status_change ();
+	#endif
 	free (name);

 	add_event_all (EV_OPTIONS, NULL);
@@ -1752,6 +1775,9 @@ void server_loop (int list_sock)

 	close_clients ();
 	clients_cleanup ();
+	#ifdef HAVE_MPRIS
+	pthread_join (mpris_tid, NULL);
+	#endif
 	close (list_sock);
 	server_shutdown ();
 }
@@ -1783,6 +1809,9 @@ void set_info_avg_bitrate (const int avg_bitrate)
 /* Notify the client about change of the player state. */
 void state_change ()
 {
+	#ifdef HAVE_MPRIS
+	mpris_status_change ();
+	#endif
 	add_event_all (EV_STATE, NULL);
 }

diff --git a/server.h b/server.h
index 2f93939..cf3037c 100644
--- a/server.h
+++ b/server.h
@@ -25,6 +25,7 @@ void tags_response (const int client_id, const char *file,
 void ev_audio_start ();
 void ev_audio_stop ();
 void server_queue_pop (const char *filename);
+void add_event_all (const int event, const void *data);

 #ifdef __cplusplus
 }
--
2.23.0

