blob: f8920c9b85e19ab0f8902f935374fd05a8666940 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2006-2007 Nokia Corporation
* Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2012-2012 Intel Corporation
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus/gdbus.h>
#include "log.h"
#include "player.h"
#include "dbus-common.h"
#include "error.h"
#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
struct player_callback {
const struct media_player_callback *cbs;
void *user_data;
};
struct pending_req {
GDBusPendingPropertySet id;
const char *key;
const char *value;
};
struct media_player {
char *device; /* Device path */
char *path; /* Player object path */
GHashTable *settings; /* Player settings */
GHashTable *track; /* Player current track */
char *status;
uint32_t position;
GTimer *progress;
guint process_id;
struct player_callback *cb;
GSList *pending;
};
static void append_metadata(void *key, void *value, void *user_data)
{
DBusMessageIter *dict = user_data;
if (strcasecmp((char *) key, "Duration") == 0 ||
strcasecmp((char *) key, "TrackNumber") == 0 ||
strcasecmp((char *) key, "NumberOfTracks") == 0) {
uint32_t num = atoi(value);
dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num);
return;
}
dict_append_entry(dict, key, DBUS_TYPE_STRING, &value);
}
static struct pending_req *find_pending(struct media_player *mp,
const char *key)
{
GSList *l;
for (l = mp->pending; l; l = l->next) {
struct pending_req *p = l->data;
if (strcasecmp(key, p->key) == 0)
return p;
}
return NULL;
}
static struct pending_req *pending_new(GDBusPendingPropertySet id,
const char *key, const char *value)
{
struct pending_req *p;
p = g_new0(struct pending_req, 1);
p->id = id;
p->key = key;
p->value = value;
return p;
}
static gboolean get_position(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_player *mp = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &mp->position);
return TRUE;
}
static gboolean status_exists(const GDBusPropertyTable *property, void *data)
{
struct media_player *mp = data;
return mp->status != NULL;
}
static gboolean get_status(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_player *mp = data;
if (mp->status == NULL)
return FALSE;
DBG("%s", mp->status);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->status);
return TRUE;
}
static gboolean setting_exists(const GDBusPropertyTable *property, void *data)
{
struct media_player *mp = data;
const char *value;
value = g_hash_table_lookup(mp->settings, property->name);
return value ? TRUE : FALSE;
}
static gboolean get_setting(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_player *mp = data;
const char *value;
value = g_hash_table_lookup(mp->settings, property->name);
if (value == NULL)
return FALSE;
DBG("%s %s", property->name, value);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value);
return TRUE;
}
static void player_set_setting(struct media_player *mp,
GDBusPendingPropertySet id,
const char *key, const char *value)
{
struct player_callback *cb = mp->cb;
struct pending_req *p;
if (cb == NULL || cb->cbs->set_setting == NULL) {
g_dbus_pending_property_error(id,
ERROR_INTERFACE ".NotSupported",
"Operation is not supported");
return;
}
p = find_pending(mp, key);
if (p != NULL) {
g_dbus_pending_property_error(id,
ERROR_INTERFACE ".InProgress",
"Operation already in progress");
return;
}
if (!cb->cbs->set_setting(mp, key, value, cb->user_data)) {
g_dbus_pending_property_error(id,
ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
return;
}
p = pending_new(id, key, value);
mp->pending = g_slist_append(mp->pending, p);
}
static void set_setting(const GDBusPropertyTable *property,
DBusMessageIter *iter, GDBusPendingPropertySet id,
void *data)
{
struct media_player *mp = data;
const char *value;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) {
g_dbus_pending_property_error(id,
ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
return;
}
dbus_message_iter_get_basic(iter, &value);
player_set_setting(mp, id, property->name, value);
}
static gboolean get_track(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_player *mp = data;
DBusMessageIter dict;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
g_hash_table_foreach(mp->track, append_metadata, &dict);
dbus_message_iter_close_container(iter, &dict);
return TRUE;
}
static gboolean get_device(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_player *mp = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->device);
return TRUE;
}
static DBusMessage *media_player_play(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->play == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->play(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_pause(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->pause == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->pause(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_stop(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->stop == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->stop(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_next(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->next == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->next(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_previous(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->previous == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->previous(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_fast_forward(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->fast_forward == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->fast_forward(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct media_player *mp = data;
struct player_callback *cb = mp->cb;
int err;
if (cb->cbs->rewind == NULL)
return btd_error_not_supported(msg);
err = cb->cbs->rewind(mp, cb->user_data);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static const GDBusMethodTable media_player_methods[] = {
{ GDBUS_EXPERIMENTAL_METHOD("Play", NULL, NULL, media_player_play) },
{ GDBUS_EXPERIMENTAL_METHOD("Pause", NULL, NULL, media_player_pause) },
{ GDBUS_EXPERIMENTAL_METHOD("Stop", NULL, NULL, media_player_stop) },
{ GDBUS_EXPERIMENTAL_METHOD("Next", NULL, NULL, media_player_next) },
{ GDBUS_EXPERIMENTAL_METHOD("Previous", NULL, NULL,
media_player_previous) },
{ GDBUS_EXPERIMENTAL_METHOD("FastForward", NULL, NULL,
media_player_fast_forward) },
{ GDBUS_EXPERIMENTAL_METHOD("Rewind", NULL, NULL,
media_player_rewind) },
{ }
};
static const GDBusSignalTable media_player_signals[] = {
{ }
};
static const GDBusPropertyTable media_player_properties[] = {
{ "Position", "u", get_position, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Status", "s", get_status, NULL, status_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Equalizer", "s", get_setting, set_setting, setting_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Repeat", "s", get_setting, set_setting, setting_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Shuffle", "s", get_setting, set_setting, setting_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Scan", "s", get_setting, set_setting, setting_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Track", "a{sv}", get_track, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Device", "s", get_device, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ }
};
void media_player_destroy(struct media_player *mp)
{
DBG("%s", mp->path);
g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path,
MEDIA_PLAYER_INTERFACE);
if (mp->track)
g_hash_table_unref(mp->track);
if (mp->settings)
g_hash_table_unref(mp->settings);
if (mp->process_id > 0)
g_source_remove(mp->process_id);
g_slist_free_full(mp->pending, g_free);
g_timer_destroy(mp->progress);
g_free(mp->cb);
g_free(mp->status);
g_free(mp->path);
g_free(mp->device);
g_free(mp);
}
struct media_player *media_player_controller_create(const char *path)
{
struct media_player *mp;
mp = g_new0(struct media_player, 1);
mp->device = g_strdup(path);
mp->path = g_strdup_printf("%s/player1", path);
mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
mp->track = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
mp->progress = g_timer_new();
if (!g_dbus_register_interface(btd_get_dbus_connection(),
mp->path, MEDIA_PLAYER_INTERFACE,
media_player_methods,
media_player_signals,
media_player_properties, mp, NULL)) {
error("D-Bus failed to register %s path", mp->path);
media_player_destroy(mp);
return NULL;
}
DBG("%s", mp->path);
return mp;
}
static uint32_t media_player_get_position(struct media_player *mp)
{
double timedelta;
uint32_t sec, msec;
if (g_strcmp0(mp->status, "playing") != 0)
return mp->position;
timedelta = g_timer_elapsed(mp->progress, NULL);
sec = (uint32_t) timedelta;
msec = (uint32_t) ((timedelta - sec) * 1000);
return mp->position + sec * 1000 + msec;
}
void media_player_set_position(struct media_player *mp, uint32_t position)
{
DBG("%u", position);
mp->position = position;
g_timer_start(mp->progress);
g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
MEDIA_PLAYER_INTERFACE, "Position");
}
void media_player_set_setting(struct media_player *mp, const char *key,
const char *value)
{
char *curval;
struct pending_req *p;
DBG("%s: %s", key, value);
if (strcasecmp(key, "Error") == 0) {
p = g_slist_nth_data(mp->pending, 0);
if (p == NULL)
return;
g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".Failed",
value);
goto send;
}
curval = g_hash_table_lookup(mp->settings, key);
if (g_strcmp0(curval, value) == 0)
goto done;
g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value));
g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
MEDIA_PLAYER_INTERFACE, key);
done:
p = find_pending(mp, key);
if (p == NULL)
return;
if (strcasecmp(value, p->value) == 0)
g_dbus_pending_property_success(p->id);
else
g_dbus_pending_property_error(p->id,
ERROR_INTERFACE ".NotSupported",
"Operation is not supported");
send:
mp->pending = g_slist_remove(mp->pending, p);
g_free(p);
return;
}
const char *media_player_get_status(struct media_player *mp)
{
return mp->status;
}
void media_player_set_status(struct media_player *mp, const char *status)
{
DBG("%s", status);
if (g_strcmp0(mp->status, status) == 0)
return;
g_free(mp->status);
mp->status = g_strdup(status);
g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
MEDIA_PLAYER_INTERFACE, "Status");
mp->position = media_player_get_position(mp);
g_timer_start(mp->progress);
}
static gboolean process_metadata_changed(void *user_data)
{
struct media_player *mp = user_data;
mp->process_id = 0;
g_dbus_emit_property_changed(btd_get_dbus_connection(),
mp->path, MEDIA_PLAYER_INTERFACE,
"Track");
return FALSE;
}
void media_player_set_metadata(struct media_player *mp, const char *key,
void *data, size_t len)
{
char *value, *curval;
value = g_strndup(data, len);
DBG("%s: %s", key, value);
curval = g_hash_table_lookup(mp->track, key);
if (g_strcmp0(curval, value) == 0) {
g_free(value);
return;
}
if (mp->process_id == 0) {
g_hash_table_remove_all(mp->track);
mp->process_id = g_idle_add(process_metadata_changed, mp);
}
g_hash_table_replace(mp->track, g_strdup(key), value);
}
void media_player_set_callbacks(struct media_player *mp,
const struct media_player_callback *cbs,
void *user_data)
{
struct player_callback *cb;
if (mp->cb)
g_free(mp->cb);
cb = g_new0(struct player_callback, 1);
cb->cbs = cbs;
cb->user_data = user_data;
mp->cb = cb;
}