3 * BlueZ - Bluetooth protocol stack for Linux
5 * Copyright (C) 2006-2007 Nokia Corporation
6 * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
7 * Copyright (C) 2012-2012 Intel Corporation
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
38 #include <dbus/dbus.h>
39 #include <gdbus/gdbus.h>
42 #include "src/dbus-common.h"
43 #include "src/error.h"
47 #define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
48 #define MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
49 #define MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
51 struct player_callback {
52 const struct media_player_callback *cbs;
57 GDBusPendingPropertySet id;
63 struct media_player *player;
64 char *path; /* Item object path */
65 char *name; /* Item name */
66 player_item_type_t type; /* Item type */
67 player_folder_type_t folder_type; /* Folder type */
68 bool playable; /* Item playable flag */
69 uint64_t uid; /* Item uid */
70 GHashTable *metadata; /* Item metadata */
74 struct media_folder *parent;
75 struct media_item *item; /* Folder item */
76 uint32_t number_of_items;/* Number of items */
83 char *device; /* Device path */
84 char *name; /* Player name */
85 char *type; /* Player type */
86 char *subtype; /* Player subtype */
87 bool browsable; /* Player browsing feature */
88 bool searchable; /* Player searching feature */
89 struct media_folder *scope; /* Player current scope */
90 struct media_folder *folder; /* Player current folder */
91 struct media_folder *search; /* Player search folder */
92 struct media_folder *playlist; /* Player current playlist */
93 char *path; /* Player object path */
94 GHashTable *settings; /* Player settings */
95 GHashTable *track; /* Player current track */
100 struct player_callback *cb;
105 static void append_track(void *key, void *value, void *user_data)
107 DBusMessageIter *dict = user_data;
108 const char *strkey = key;
110 if (strcasecmp(strkey, "Duration") == 0 ||
111 strcasecmp(strkey, "TrackNumber") == 0 ||
112 strcasecmp(strkey, "NumberOfTracks") == 0) {
113 uint32_t num = atoi(value);
114 dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num);
115 } else if (strcasecmp(strkey, "Item") == 0) {
116 dict_append_entry(dict, key, DBUS_TYPE_OBJECT_PATH, &value);
118 dict_append_entry(dict, key, DBUS_TYPE_STRING, &value);
122 static struct pending_req *find_pending(struct media_player *mp,
127 for (l = mp->pending; l; l = l->next) {
128 struct pending_req *p = l->data;
130 if (strcasecmp(key, p->key) == 0)
137 static struct pending_req *pending_new(GDBusPendingPropertySet id,
138 const char *key, const char *value)
140 struct pending_req *p;
142 p = g_new0(struct pending_req, 1);
150 static uint32_t media_player_get_position(struct media_player *mp)
155 if (g_strcmp0(mp->status, "playing") != 0 ||
156 mp->position == UINT32_MAX)
159 timedelta = g_timer_elapsed(mp->progress, NULL);
161 sec = (uint32_t) timedelta;
162 msec = (uint32_t) ((timedelta - sec) * 1000);
164 return mp->position + sec * 1000 + msec;
167 static gboolean get_position(const GDBusPropertyTable *property,
168 DBusMessageIter *iter, void *data)
170 struct media_player *mp = data;
173 position = media_player_get_position(mp);
175 dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &position);
180 static gboolean status_exists(const GDBusPropertyTable *property, void *data)
182 struct media_player *mp = data;
184 return mp->status != NULL;
187 static gboolean get_status(const GDBusPropertyTable *property,
188 DBusMessageIter *iter, void *data)
190 struct media_player *mp = data;
192 if (mp->status == NULL)
195 DBG("%s", mp->status);
197 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->status);
202 static gboolean setting_exists(const GDBusPropertyTable *property, void *data)
204 struct media_player *mp = data;
207 value = g_hash_table_lookup(mp->settings, property->name);
209 return value ? TRUE : FALSE;
212 static gboolean get_setting(const GDBusPropertyTable *property,
213 DBusMessageIter *iter, void *data)
215 struct media_player *mp = data;
218 value = g_hash_table_lookup(mp->settings, property->name);
222 DBG("%s %s", property->name, value);
224 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value);
229 static void player_set_setting(struct media_player *mp,
230 GDBusPendingPropertySet id,
231 const char *key, const char *value)
233 struct player_callback *cb = mp->cb;
234 struct pending_req *p;
236 if (cb == NULL || cb->cbs->set_setting == NULL) {
237 g_dbus_pending_property_error(id,
238 ERROR_INTERFACE ".NotSupported",
239 "Operation is not supported");
243 p = find_pending(mp, key);
245 g_dbus_pending_property_error(id,
246 ERROR_INTERFACE ".InProgress",
247 "Operation already in progress");
251 if (!cb->cbs->set_setting(mp, key, value, cb->user_data)) {
252 g_dbus_pending_property_error(id,
253 ERROR_INTERFACE ".InvalidArguments",
254 "Invalid arguments in method call");
258 p = pending_new(id, key, value);
260 mp->pending = g_slist_append(mp->pending, p);
263 static void set_setting(const GDBusPropertyTable *property,
264 DBusMessageIter *iter, GDBusPendingPropertySet id,
267 struct media_player *mp = data;
268 const char *value, *current;
270 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) {
271 g_dbus_pending_property_error(id,
272 ERROR_INTERFACE ".InvalidArguments",
273 "Invalid arguments in method call");
277 dbus_message_iter_get_basic(iter, &value);
279 current = g_hash_table_lookup(mp->settings, property->name);
280 if (g_strcmp0(current, value) == 0) {
281 g_dbus_pending_property_success(id);
285 player_set_setting(mp, id, property->name, value);
288 static gboolean track_exists(const GDBusPropertyTable *property, void *data)
290 struct media_player *mp = data;
292 return g_hash_table_size(mp->track) != 0;
295 static gboolean get_track(const GDBusPropertyTable *property,
296 DBusMessageIter *iter, void *data)
298 struct media_player *mp = data;
299 DBusMessageIter dict;
301 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
302 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
303 DBUS_TYPE_STRING_AS_STRING
304 DBUS_TYPE_VARIANT_AS_STRING
305 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
308 g_hash_table_foreach(mp->track, append_track, &dict);
310 dbus_message_iter_close_container(iter, &dict);
315 static gboolean get_device(const GDBusPropertyTable *property,
316 DBusMessageIter *iter, void *data)
318 struct media_player *mp = data;
320 dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
326 static gboolean name_exists(const GDBusPropertyTable *property, void *data)
328 struct media_player *mp = data;
330 return mp->name != NULL;
333 static gboolean get_name(const GDBusPropertyTable *property,
334 DBusMessageIter *iter, void *data)
336 struct media_player *mp = data;
338 if (mp->name == NULL)
343 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->name);
348 static gboolean type_exists(const GDBusPropertyTable *property, void *data)
350 struct media_player *mp = data;
352 return mp->type != NULL;
355 static gboolean get_type(const GDBusPropertyTable *property,
356 DBusMessageIter *iter, void *data)
358 struct media_player *mp = data;
360 if (mp->type == NULL)
365 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->type);
370 static gboolean subtype_exists(const GDBusPropertyTable *property, void *data)
372 struct media_player *mp = data;
374 return mp->subtype != NULL;
377 static gboolean get_subtype(const GDBusPropertyTable *property,
378 DBusMessageIter *iter, void *data)
380 struct media_player *mp = data;
382 if (mp->subtype == NULL)
385 DBG("%s", mp->subtype);
387 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->subtype);
392 static gboolean browsable_exists(const GDBusPropertyTable *property, void *data)
394 struct media_player *mp = data;
396 return mp->scope != NULL;
399 static gboolean get_browsable(const GDBusPropertyTable *property,
400 DBusMessageIter *iter, void *data)
402 struct media_player *mp = data;
405 if (mp->scope == NULL)
408 DBG("%s", mp->browsable ? "true" : "false");
410 value = mp->browsable;
412 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
417 static gboolean searchable_exists(const GDBusPropertyTable *property,
420 struct media_player *mp = data;
422 return mp->scope != NULL;
425 static gboolean get_searchable(const GDBusPropertyTable *property,
426 DBusMessageIter *iter, void *data)
428 struct media_player *mp = data;
431 if (mp->folder == NULL)
434 DBG("%s", mp->searchable ? "true" : "false");
436 value = mp->searchable;
438 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
443 static gboolean playlist_exists(const GDBusPropertyTable *property,
446 struct media_player *mp = data;
448 return mp->playlist != NULL;
451 static gboolean get_playlist(const GDBusPropertyTable *property,
452 DBusMessageIter *iter, void *data)
454 struct media_player *mp = data;
455 struct media_folder *playlist = mp->playlist;
457 if (playlist == NULL)
460 dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
461 &playlist->item->path);
466 static DBusMessage *media_player_play(DBusConnection *conn, DBusMessage *msg,
469 struct media_player *mp = data;
470 struct player_callback *cb = mp->cb;
473 if (cb->cbs->play == NULL)
474 return btd_error_not_supported(msg);
476 err = cb->cbs->play(mp, cb->user_data);
478 return btd_error_failed(msg, strerror(-err));
480 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
483 static DBusMessage *media_player_pause(DBusConnection *conn, DBusMessage *msg,
486 struct media_player *mp = data;
487 struct player_callback *cb = mp->cb;
490 if (cb->cbs->pause == NULL)
491 return btd_error_not_supported(msg);
493 err = cb->cbs->pause(mp, cb->user_data);
495 return btd_error_failed(msg, strerror(-err));
497 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
500 static DBusMessage *media_player_stop(DBusConnection *conn, DBusMessage *msg,
503 struct media_player *mp = data;
504 struct player_callback *cb = mp->cb;
507 if (cb->cbs->stop == NULL)
508 return btd_error_not_supported(msg);
510 err = cb->cbs->stop(mp, cb->user_data);
512 return btd_error_failed(msg, strerror(-err));
514 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
517 static DBusMessage *media_player_next(DBusConnection *conn, DBusMessage *msg,
520 struct media_player *mp = data;
521 struct player_callback *cb = mp->cb;
524 if (cb->cbs->next == NULL)
525 return btd_error_not_supported(msg);
527 err = cb->cbs->next(mp, cb->user_data);
529 return btd_error_failed(msg, strerror(-err));
531 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
534 static DBusMessage *media_player_previous(DBusConnection *conn,
535 DBusMessage *msg, void *data)
537 struct media_player *mp = data;
538 struct player_callback *cb = mp->cb;
541 if (cb->cbs->previous == NULL)
542 return btd_error_not_supported(msg);
544 err = cb->cbs->previous(mp, cb->user_data);
546 return btd_error_failed(msg, strerror(-err));
548 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
551 static DBusMessage *media_player_fast_forward(DBusConnection *conn,
552 DBusMessage *msg, void *data)
554 struct media_player *mp = data;
555 struct player_callback *cb = mp->cb;
558 if (cb->cbs->fast_forward == NULL)
559 return btd_error_not_supported(msg);
561 err = cb->cbs->fast_forward(mp, cb->user_data);
563 return btd_error_failed(msg, strerror(-err));
565 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
568 static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg,
571 struct media_player *mp = data;
572 struct player_callback *cb = mp->cb;
575 if (cb->cbs->rewind == NULL)
576 return btd_error_not_supported(msg);
578 err = cb->cbs->rewind(mp, cb->user_data);
580 return btd_error_failed(msg, strerror(-err));
582 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
585 static void parse_folder_list(gpointer data, gpointer user_data)
587 struct media_item *item = data;
588 DBusMessageIter *array = user_data;
589 DBusMessageIter entry;
591 dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL,
594 dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
597 g_dbus_get_properties(btd_get_dbus_connection(), item->path,
598 MEDIA_ITEM_INTERFACE, &entry);
600 dbus_message_iter_close_container(array, &entry);
603 void media_player_change_folder_complete(struct media_player *mp,
604 const char *path, int ret)
606 struct media_folder *folder = mp->scope;
609 if (folder == NULL || folder->msg == NULL)
613 reply = btd_error_failed(folder->msg, strerror(-ret));
617 media_player_set_folder(mp, path, ret);
619 reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID);
622 g_dbus_send_message(btd_get_dbus_connection(), reply);
623 dbus_message_unref(folder->msg);
627 void media_player_list_complete(struct media_player *mp, GSList *items,
630 struct media_folder *folder = mp->scope;
632 DBusMessageIter iter, array;
634 if (folder == NULL || folder->msg == NULL)
638 reply = btd_error_failed(folder->msg, strerror(-err));
642 reply = dbus_message_new_method_return(folder->msg);
644 dbus_message_iter_init_append(reply, &iter);
646 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
647 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
648 DBUS_TYPE_OBJECT_PATH_AS_STRING
649 DBUS_TYPE_ARRAY_AS_STRING
650 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
651 DBUS_TYPE_STRING_AS_STRING
652 DBUS_TYPE_VARIANT_AS_STRING
653 DBUS_DICT_ENTRY_END_CHAR_AS_STRING
654 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
657 g_slist_foreach(items, parse_folder_list, &array);
658 dbus_message_iter_close_container(&iter, &array);
661 g_dbus_send_message(btd_get_dbus_connection(), reply);
662 dbus_message_unref(folder->msg);
666 static struct media_item *
667 media_player_create_subfolder(struct media_player *mp, const char *name,
670 struct media_folder *folder = mp->scope;
671 struct media_item *item;
674 path = g_strdup_printf("%s/%s", folder->item->name, name);
678 item = media_player_create_item(mp, path, PLAYER_ITEM_TYPE_FOLDER,
685 void media_player_search_complete(struct media_player *mp, int ret)
687 struct media_folder *folder = mp->scope;
688 struct media_folder *search = mp->search;
691 if (folder == NULL || folder->msg == NULL)
695 reply = btd_error_failed(folder->msg, strerror(-ret));
699 if (search == NULL) {
700 search = g_new0(struct media_folder, 1);
701 search->item = media_player_create_subfolder(mp, "search", 0);
703 mp->folders = g_slist_prepend(mp->folders, search);
706 search->number_of_items = ret;
708 reply = g_dbus_create_reply(folder->msg,
709 DBUS_TYPE_OBJECT_PATH, &search->item->path,
713 g_dbus_send_message(btd_get_dbus_connection(), reply);
714 dbus_message_unref(folder->msg);
718 static const GDBusMethodTable media_player_methods[] = {
719 { GDBUS_EXPERIMENTAL_METHOD("Play", NULL, NULL, media_player_play) },
720 { GDBUS_EXPERIMENTAL_METHOD("Pause", NULL, NULL, media_player_pause) },
721 { GDBUS_EXPERIMENTAL_METHOD("Stop", NULL, NULL, media_player_stop) },
722 { GDBUS_EXPERIMENTAL_METHOD("Next", NULL, NULL, media_player_next) },
723 { GDBUS_EXPERIMENTAL_METHOD("Previous", NULL, NULL,
724 media_player_previous) },
725 { GDBUS_EXPERIMENTAL_METHOD("FastForward", NULL, NULL,
726 media_player_fast_forward) },
727 { GDBUS_EXPERIMENTAL_METHOD("Rewind", NULL, NULL,
728 media_player_rewind) },
732 static const GDBusSignalTable media_player_signals[] = {
736 static const GDBusPropertyTable media_player_properties[] = {
737 { "Name", "s", get_name, NULL, name_exists,
738 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
739 { "Type", "s", get_type, NULL, type_exists,
740 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
741 { "Subtype", "s", get_subtype, NULL, subtype_exists,
742 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
743 { "Position", "u", get_position, NULL, NULL,
744 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
745 { "Status", "s", get_status, NULL, status_exists,
746 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
747 { "Equalizer", "s", get_setting, set_setting, setting_exists,
748 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
749 { "Repeat", "s", get_setting, set_setting, setting_exists,
750 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
751 { "Shuffle", "s", get_setting, set_setting, setting_exists,
752 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
753 { "Scan", "s", get_setting, set_setting, setting_exists,
754 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
755 { "Track", "a{sv}", get_track, NULL, track_exists,
756 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
757 { "Device", "o", get_device, NULL, NULL,
758 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
759 { "Browsable", "b", get_browsable, NULL, browsable_exists,
760 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
761 { "Searchable", "b", get_searchable, NULL, searchable_exists,
762 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
763 { "Playlist", "o", get_playlist, NULL, playlist_exists,
764 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
768 static DBusMessage *media_folder_search(DBusConnection *conn, DBusMessage *msg,
771 struct media_player *mp = data;
772 struct media_folder *folder = mp->scope;
773 struct player_callback *cb = mp->cb;
774 DBusMessageIter iter;
778 dbus_message_iter_init(msg, &iter);
780 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
781 return btd_error_failed(msg, strerror(EINVAL));
783 dbus_message_iter_get_basic(&iter, &string);
785 if (!mp->searchable || folder != mp->folder)
786 return btd_error_failed(msg, strerror(ENOTSUP));
788 if (folder->msg != NULL)
789 return btd_error_failed(msg, strerror(EINVAL));
791 if (cb->cbs->search == NULL)
792 return btd_error_failed(msg, strerror(ENOTSUP));
794 err = cb->cbs->search(mp, string, cb->user_data);
796 return btd_error_failed(msg, strerror(-err));
798 folder->msg = dbus_message_ref(msg);
803 static int parse_filters(struct media_player *player, DBusMessageIter *iter,
804 uint32_t *start, uint32_t *end)
806 struct media_folder *folder = player->scope;
807 DBusMessageIter dict;
811 *end = folder->number_of_items ? folder->number_of_items - 1 :
814 ctype = dbus_message_iter_get_arg_type(iter);
815 if (ctype != DBUS_TYPE_ARRAY)
818 dbus_message_iter_recurse(iter, &dict);
820 while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
822 DBusMessageIter entry, var;
825 if (ctype != DBUS_TYPE_DICT_ENTRY)
828 dbus_message_iter_recurse(&dict, &entry);
829 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
832 dbus_message_iter_get_basic(&entry, &key);
833 dbus_message_iter_next(&entry);
835 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
838 dbus_message_iter_recurse(&entry, &var);
840 if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32)
843 if (strcasecmp(key, "Start") == 0)
844 dbus_message_iter_get_basic(&var, start);
845 else if (strcasecmp(key, "End") == 0)
846 dbus_message_iter_get_basic(&var, end);
848 dbus_message_iter_next(&dict);
851 if (folder->number_of_items > 0 && *end > folder->number_of_items)
852 *end = folder->number_of_items;
857 static DBusMessage *media_folder_list_items(DBusConnection *conn,
858 DBusMessage *msg, void *data)
860 struct media_player *mp = data;
861 struct media_folder *folder = mp->scope;
862 struct player_callback *cb = mp->cb;
863 DBusMessageIter iter;
867 dbus_message_iter_init(msg, &iter);
869 if (parse_filters(mp, &iter, &start, &end) < 0)
870 return btd_error_invalid_args(msg);
872 if (cb->cbs->list_items == NULL)
873 return btd_error_not_supported(msg);
875 if (folder->msg != NULL)
876 return btd_error_failed(msg, strerror(EBUSY));
878 err = cb->cbs->list_items(mp, folder->item->name, start, end,
881 return btd_error_failed(msg, strerror(-err));
883 folder->msg = dbus_message_ref(msg);
888 static void media_item_free(struct media_item *item)
890 if (item->metadata != NULL)
891 g_hash_table_unref(item->metadata);
898 static void media_item_destroy(void *data)
900 struct media_item *item = data;
902 DBG("%s", item->path);
904 g_dbus_unregister_interface(btd_get_dbus_connection(), item->path,
905 MEDIA_ITEM_INTERFACE);
907 media_item_free(item);
910 static void media_folder_destroy(void *data)
912 struct media_folder *folder = data;
914 g_slist_free_full(folder->subfolders, media_folder_destroy);
915 g_slist_free_full(folder->items, media_item_destroy);
917 if (folder->msg != NULL)
918 dbus_message_unref(folder->msg);
920 media_item_destroy(folder->item);
924 static void media_player_change_scope(struct media_player *mp,
925 struct media_folder *folder)
927 if (mp->scope == folder)
930 DBG("%s", folder->item->name);
932 /* Skip setting current folder if folder is current playlist/search */
933 if (folder == mp->playlist || folder == mp->search)
938 /* Skip item cleanup if scope is the current playlist */
939 if (mp->scope == mp->playlist)
943 g_slist_free_full(mp->scope->items, media_item_destroy);
944 mp->scope->items = NULL;
946 /* Destroy search folder if it exists and is not being set as scope */
947 if (mp->search != NULL && folder != mp->search) {
948 mp->folders = g_slist_remove(mp->folders, mp->search);
949 media_folder_destroy(mp->search);
956 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
957 MEDIA_FOLDER_INTERFACE, "Name");
958 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
959 MEDIA_FOLDER_INTERFACE, "NumberOfItems");
962 static struct media_folder *find_folder(GSList *folders, const char *pattern)
966 for (l = folders; l; l = l->next) {
967 struct media_folder *folder = l->data;
969 if (g_str_equal(folder->item->name, pattern))
972 if (g_str_equal(folder->item->path, pattern))
975 folder = find_folder(folder->subfolders, pattern);
983 static struct media_folder *media_player_find_folder(struct media_player *mp,
986 return find_folder(mp->folders, pattern);
989 static DBusMessage *media_folder_change_folder(DBusConnection *conn,
990 DBusMessage *msg, void *data)
992 struct media_player *mp = data;
993 struct media_folder *folder = mp->scope;
994 struct player_callback *cb = mp->cb;
998 if (!dbus_message_get_args(msg, NULL,
999 DBUS_TYPE_OBJECT_PATH, &path,
1001 return btd_error_failed(msg, strerror(EINVAL));
1003 if (folder->msg != NULL)
1004 return btd_error_failed(msg, strerror(EBUSY));
1006 folder = media_player_find_folder(mp, path);
1008 return btd_error_failed(msg, strerror(EINVAL));
1010 if (mp->scope == folder)
1011 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1013 if (folder == mp->playlist || folder == mp->folder ||
1014 folder == mp->search) {
1015 media_player_change_scope(mp, folder);
1016 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1019 if (cb->cbs->change_folder == NULL)
1020 return btd_error_failed(msg, strerror(ENOTSUP));
1022 err = cb->cbs->change_folder(mp, folder->item->name, folder->item->uid,
1025 return btd_error_failed(msg, strerror(-err));
1027 mp->scope->msg = dbus_message_ref(msg);
1032 static gboolean folder_name_exists(const GDBusPropertyTable *property,
1035 struct media_player *mp = data;
1036 struct media_folder *folder = mp->scope;
1038 if (folder == NULL || folder->item == NULL)
1041 return folder->item->name != NULL;
1044 static gboolean get_folder_name(const GDBusPropertyTable *property,
1045 DBusMessageIter *iter, void *data)
1047 struct media_player *mp = data;
1048 struct media_folder *folder = mp->scope;
1050 if (folder == NULL || folder->item == NULL)
1053 DBG("%s", folder->item->name);
1055 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
1056 &folder->item->name);
1061 static gboolean items_exists(const GDBusPropertyTable *property, void *data)
1063 struct media_player *mp = data;
1065 return mp->scope != NULL;
1068 static gboolean get_items(const GDBusPropertyTable *property,
1069 DBusMessageIter *iter, void *data)
1071 struct media_player *mp = data;
1072 struct media_folder *folder = mp->scope;
1077 DBG("%u", folder->number_of_items);
1079 dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
1080 &folder->number_of_items);
1085 static const GDBusMethodTable media_folder_methods[] = {
1086 { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Search",
1087 GDBUS_ARGS({ "string", "s" }, { "filter", "a{sv}" }),
1088 GDBUS_ARGS({ "folder", "o" }),
1089 media_folder_search) },
1090 { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ListItems",
1091 GDBUS_ARGS({ "filter", "a{sv}" }),
1092 GDBUS_ARGS({ "items", "a{oa{sv}}" }),
1093 media_folder_list_items) },
1094 { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ChangeFolder",
1095 GDBUS_ARGS({ "folder", "o" }), NULL,
1096 media_folder_change_folder) },
1100 static const GDBusPropertyTable media_folder_properties[] = {
1101 { "Name", "s", get_folder_name, NULL, folder_name_exists,
1102 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1103 { "NumberOfItems", "u", get_items, NULL, items_exists,
1104 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1108 static void media_player_set_scope(struct media_player *mp,
1109 struct media_folder *folder)
1111 if (mp->scope == NULL) {
1112 if (!g_dbus_register_interface(btd_get_dbus_connection(),
1113 mp->path, MEDIA_FOLDER_INTERFACE,
1114 media_folder_methods,
1116 media_folder_properties, mp, NULL)) {
1117 error("D-Bus failed to register %s on %s path",
1118 MEDIA_FOLDER_INTERFACE, mp->path);
1125 return media_player_change_scope(mp, folder);
1128 void media_player_destroy(struct media_player *mp)
1130 DBG("%s", mp->path);
1132 g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path,
1133 MEDIA_PLAYER_INTERFACE);
1136 g_hash_table_unref(mp->track);
1139 g_hash_table_unref(mp->settings);
1141 if (mp->process_id > 0)
1142 g_source_remove(mp->process_id);
1145 g_dbus_unregister_interface(btd_get_dbus_connection(),
1147 MEDIA_FOLDER_INTERFACE);
1149 g_slist_free_full(mp->pending, g_free);
1150 g_slist_free_full(mp->folders, media_folder_destroy);
1152 g_timer_destroy(mp->progress);
1157 g_free(mp->subtype);
1163 struct media_player *media_player_controller_create(const char *path,
1166 struct media_player *mp;
1168 mp = g_new0(struct media_player, 1);
1169 mp->device = g_strdup(path);
1170 mp->path = g_strdup_printf("%s/player%u", path, id);
1171 mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal,
1173 mp->track = g_hash_table_new_full(g_str_hash, g_str_equal,
1175 mp->progress = g_timer_new();
1177 if (!g_dbus_register_interface(btd_get_dbus_connection(),
1178 mp->path, MEDIA_PLAYER_INTERFACE,
1179 media_player_methods,
1180 media_player_signals,
1181 media_player_properties, mp, NULL)) {
1182 error("D-Bus failed to register %s path", mp->path);
1183 media_player_destroy(mp);
1187 DBG("%s", mp->path);
1192 void media_player_set_duration(struct media_player *mp, uint32_t duration)
1194 char *value, *curval;
1196 DBG("%u", duration);
1198 /* Only update duration if track exists */
1199 if (g_hash_table_size(mp->track) == 0)
1202 /* Ignore if duration is already set */
1203 curval = g_hash_table_lookup(mp->track, "Duration");
1207 value = g_strdup_printf("%u", duration);
1209 g_hash_table_replace(mp->track, g_strdup("Duration"), value);
1211 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1212 mp->path, MEDIA_PLAYER_INTERFACE,
1216 void media_player_set_position(struct media_player *mp, uint32_t position)
1218 DBG("%u", position);
1220 /* Only update duration if track exists */
1221 if (g_hash_table_size(mp->track) == 0)
1224 mp->position = position;
1225 g_timer_start(mp->progress);
1227 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1228 MEDIA_PLAYER_INTERFACE, "Position");
1231 void media_player_set_setting(struct media_player *mp, const char *key,
1235 struct pending_req *p;
1237 DBG("%s: %s", key, value);
1239 if (strcasecmp(key, "Error") == 0) {
1240 p = g_slist_nth_data(mp->pending, 0);
1244 g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".Failed",
1249 curval = g_hash_table_lookup(mp->settings, key);
1250 if (g_strcmp0(curval, value) == 0)
1253 g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value));
1254 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1255 MEDIA_PLAYER_INTERFACE, key);
1258 p = find_pending(mp, key);
1262 if (strcasecmp(value, p->value) == 0)
1263 g_dbus_pending_property_success(p->id);
1265 g_dbus_pending_property_error(p->id,
1266 ERROR_INTERFACE ".NotSupported",
1267 "Operation is not supported");
1270 mp->pending = g_slist_remove(mp->pending, p);
1276 const char *media_player_get_status(struct media_player *mp)
1281 void media_player_set_status(struct media_player *mp, const char *status)
1285 if (g_strcmp0(mp->status, status) == 0)
1289 mp->status = g_strdup(status);
1291 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1292 MEDIA_PLAYER_INTERFACE, "Status");
1294 mp->position = media_player_get_position(mp);
1295 g_timer_start(mp->progress);
1298 static gboolean process_metadata_changed(void *user_data)
1300 struct media_player *mp = user_data;
1305 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1306 mp->path, MEDIA_PLAYER_INTERFACE,
1309 item = g_hash_table_lookup(mp->track, "Item");
1313 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1314 item, MEDIA_ITEM_INTERFACE,
1320 void media_player_set_metadata(struct media_player *mp,
1321 struct media_item *item, const char *key,
1322 void *data, size_t len)
1324 char *value, *curval;
1326 value = g_strndup(data, len);
1328 DBG("%s: %s", key, value);
1330 curval = g_hash_table_lookup(mp->track, key);
1331 if (g_strcmp0(curval, value) == 0) {
1336 if (mp->process_id == 0) {
1337 g_hash_table_remove_all(mp->track);
1338 mp->process_id = g_idle_add(process_metadata_changed, mp);
1341 g_hash_table_replace(mp->track, g_strdup(key), value);
1344 void media_player_set_type(struct media_player *mp, const char *type)
1346 if (g_strcmp0(mp->type, type) == 0)
1351 mp->type = g_strdup(type);
1353 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1354 mp->path, MEDIA_PLAYER_INTERFACE,
1358 void media_player_set_subtype(struct media_player *mp, const char *subtype)
1360 if (g_strcmp0(mp->subtype, subtype) == 0)
1365 mp->subtype = g_strdup(subtype);
1367 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1368 mp->path, MEDIA_PLAYER_INTERFACE,
1372 void media_player_set_name(struct media_player *mp, const char *name)
1374 if (g_strcmp0(mp->name, name) == 0)
1379 mp->name = g_strdup(name);
1381 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1382 mp->path, MEDIA_PLAYER_INTERFACE,
1386 void media_player_set_browsable(struct media_player *mp, bool enabled)
1388 if (mp->browsable == enabled)
1391 DBG("%s", enabled ? "true" : "false");
1393 mp->browsable = enabled;
1395 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1396 mp->path, MEDIA_PLAYER_INTERFACE,
1400 void media_player_set_searchable(struct media_player *mp, bool enabled)
1402 if (mp->browsable == enabled)
1405 DBG("%s", enabled ? "true" : "false");
1407 mp->searchable = enabled;
1409 g_dbus_emit_property_changed(btd_get_dbus_connection(),
1410 mp->path, MEDIA_PLAYER_INTERFACE,
1414 void media_player_set_folder(struct media_player *mp, const char *name,
1415 uint32_t number_of_items)
1417 struct media_folder *folder;
1419 DBG("%s number of items %u", name, number_of_items);
1421 folder = media_player_find_folder(mp, name);
1422 if (folder == NULL) {
1423 error("Unknown folder: %s", name);
1427 folder->number_of_items = number_of_items;
1429 media_player_set_scope(mp, folder);
1432 void media_player_set_playlist(struct media_player *mp, const char *name)
1434 struct media_folder *folder;
1438 folder = media_player_find_folder(mp, name);
1439 if (folder == NULL) {
1440 error("Unknown folder: %s", name);
1444 mp->playlist = folder;
1446 g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1447 MEDIA_PLAYER_INTERFACE, "Playlist");
1450 static struct media_item *media_folder_find_item(struct media_folder *folder,
1458 for (l = folder->items; l; l = l->next) {
1459 struct media_item *item = l->data;
1461 if (item->uid == uid)
1468 static DBusMessage *media_item_play(DBusConnection *conn, DBusMessage *msg,
1471 struct media_item *item = data;
1472 struct media_player *mp = item->player;
1473 struct player_callback *cb = mp->cb;
1476 if (!item->playable)
1477 return btd_error_failed(msg, strerror(ENOTSUP));
1479 if (cb->cbs->play_item == NULL)
1480 return btd_error_failed(msg, strerror(ENOTSUP));
1482 err = cb->cbs->play_item(mp, item->path, item->uid, cb->user_data);
1484 return btd_error_failed(msg, strerror(-err));
1486 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1489 static DBusMessage *media_item_add_to_nowplaying(DBusConnection *conn,
1490 DBusMessage *msg, void *data)
1492 struct media_item *item = data;
1493 struct media_player *mp = item->player;
1494 struct player_callback *cb = mp->cb;
1497 if (!item->playable)
1498 return btd_error_failed(msg, strerror(ENOTSUP));
1500 if (cb->cbs->play_item == NULL)
1501 return btd_error_failed(msg, strerror(ENOTSUP));
1503 err = cb->cbs->add_to_nowplaying(mp, item->path, item->uid,
1506 return btd_error_failed(msg, strerror(-err));
1508 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1511 static gboolean get_player(const GDBusPropertyTable *property,
1512 DBusMessageIter *iter, void *data)
1514 struct media_item *item = data;
1516 DBG("%s", item->player->path);
1518 dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
1519 &item->player->path);
1524 static gboolean item_name_exists(const GDBusPropertyTable *property,
1527 struct media_item *item = data;
1529 return item->name != NULL;
1532 static gboolean get_item_name(const GDBusPropertyTable *property,
1533 DBusMessageIter *iter, void *data)
1535 struct media_item *item = data;
1537 if (item->name == NULL)
1540 DBG("%s", item->name);
1542 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &item->name);
1547 static const char *type_to_string(uint8_t type)
1550 case PLAYER_ITEM_TYPE_AUDIO:
1552 case PLAYER_ITEM_TYPE_VIDEO:
1554 case PLAYER_ITEM_TYPE_FOLDER:
1561 static gboolean get_item_type(const GDBusPropertyTable *property,
1562 DBusMessageIter *iter, void *data)
1564 struct media_item *item = data;
1567 string = type_to_string(item->type);
1571 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
1576 static gboolean get_playable(const GDBusPropertyTable *property,
1577 DBusMessageIter *iter, void *data)
1579 struct media_item *item = data;
1582 DBG("%s", item->playable ? "true" : "false");
1584 value = item->playable;
1586 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
1591 static const char *folder_type_to_string(uint8_t type)
1594 case PLAYER_FOLDER_TYPE_MIXED:
1596 case PLAYER_FOLDER_TYPE_TITLES:
1598 case PLAYER_FOLDER_TYPE_ALBUMS:
1600 case PLAYER_FOLDER_TYPE_ARTISTS:
1602 case PLAYER_FOLDER_TYPE_GENRES:
1604 case PLAYER_FOLDER_TYPE_PLAYLISTS:
1606 case PLAYER_FOLDER_TYPE_YEARS:
1613 static gboolean folder_type_exists(const GDBusPropertyTable *property,
1616 struct media_item *item = data;
1618 return folder_type_to_string(item->folder_type) != NULL;
1621 static gboolean get_folder_type(const GDBusPropertyTable *property,
1622 DBusMessageIter *iter, void *data)
1624 struct media_item *item = data;
1627 string = folder_type_to_string(item->folder_type);
1633 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
1638 static gboolean metadata_exists(const GDBusPropertyTable *property, void *data)
1640 struct media_item *item = data;
1642 return item->metadata != NULL;
1645 static void append_metadata(void *key, void *value, void *user_data)
1647 DBusMessageIter *dict = user_data;
1648 const char *strkey = key;
1650 if (strcasecmp(strkey, "Item") == 0)
1653 if (strcasecmp(strkey, "Duration") == 0 ||
1654 strcasecmp(strkey, "TrackNumber") == 0 ||
1655 strcasecmp(strkey, "NumberOfTracks") == 0) {
1656 uint32_t num = atoi(value);
1657 dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num);
1659 dict_append_entry(dict, key, DBUS_TYPE_STRING, &value);
1663 static gboolean get_metadata(const GDBusPropertyTable *property,
1664 DBusMessageIter *iter, void *data)
1666 struct media_item *item = data;
1667 DBusMessageIter dict;
1669 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1670 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1671 DBUS_TYPE_STRING_AS_STRING
1672 DBUS_TYPE_VARIANT_AS_STRING
1673 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
1676 if (g_hash_table_size(item->metadata) > 0)
1677 g_hash_table_foreach(item->metadata, append_metadata, &dict);
1678 else if (item->name != NULL)
1679 dict_append_entry(&dict, "Title", DBUS_TYPE_STRING,
1682 dbus_message_iter_close_container(iter, &dict);
1687 static const GDBusMethodTable media_item_methods[] = {
1688 { GDBUS_EXPERIMENTAL_METHOD("Play", NULL, NULL,
1690 { GDBUS_EXPERIMENTAL_METHOD("AddtoNowPlaying", NULL, NULL,
1691 media_item_add_to_nowplaying) },
1695 static const GDBusPropertyTable media_item_properties[] = {
1696 { "Player", "o", get_player, NULL, NULL,
1697 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1698 { "Name", "s", get_item_name, NULL, item_name_exists,
1699 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1700 { "Type", "s", get_item_type, NULL, NULL,
1701 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1702 { "FolderType", "s", get_folder_type, NULL, folder_type_exists,
1703 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1704 { "Playable", "b", get_playable, NULL, NULL,
1705 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1706 { "Metadata", "a{sv}", get_metadata, NULL, metadata_exists,
1707 G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1711 void media_item_set_playable(struct media_item *item, bool value)
1713 if (item->playable == value)
1716 item->playable = value;
1718 g_dbus_emit_property_changed(btd_get_dbus_connection(), item->path,
1719 MEDIA_ITEM_INTERFACE, "Playable");
1722 static struct media_item *media_folder_create_item(struct media_player *mp,
1723 struct media_folder *folder,
1725 player_item_type_t type,
1728 struct media_item *item;
1729 const char *strtype;
1731 item = media_folder_find_item(folder, uid);
1735 strtype = type_to_string(type);
1736 if (strtype == NULL)
1739 DBG("%s type %s uid %" PRIu64 "", name, strtype, uid);
1741 item = g_new0(struct media_item, 1);
1746 item->path = g_strdup_printf("%s/item%" PRIu64 "",
1747 folder->item->path, uid);
1749 item->path = g_strdup_printf("%s%s", mp->path, name);
1751 item->name = g_strdup(name);
1753 item->folder_type = PLAYER_FOLDER_TYPE_INVALID;
1755 if (!g_dbus_register_interface(btd_get_dbus_connection(),
1756 item->path, MEDIA_ITEM_INTERFACE,
1759 media_item_properties, item, NULL)) {
1760 error("D-Bus failed to register %s on %s path",
1761 MEDIA_ITEM_INTERFACE, item->path);
1762 media_item_free(item);
1766 if (type != PLAYER_ITEM_TYPE_FOLDER) {
1767 folder->items = g_slist_prepend(folder->items, item);
1768 item->metadata = g_hash_table_new_full(g_str_hash, g_str_equal,
1772 DBG("%s", item->path);
1777 struct media_item *media_player_create_item(struct media_player *mp,
1779 player_item_type_t type,
1782 return media_folder_create_item(mp, mp->scope, name, type, uid);
1785 static struct media_folder *
1786 media_player_find_folder_by_uid(struct media_player *mp, uint64_t uid)
1788 struct media_folder *folder = mp->scope;
1791 for (l = folder->subfolders; l; l = l->next) {
1792 struct media_folder *folder = l->data;
1794 if (folder->item->uid == uid)
1801 struct media_item *media_player_create_folder(struct media_player *mp,
1803 player_folder_type_t type,
1806 struct media_folder *folder;
1807 struct media_item *item;
1810 folder = media_player_find_folder_by_uid(mp, uid);
1812 folder = media_player_find_folder(mp, name);
1815 return folder->item;
1818 item = media_player_create_subfolder(mp, name, uid);
1820 item = media_player_create_item(mp, name,
1821 PLAYER_ITEM_TYPE_FOLDER, uid);
1826 folder = g_new0(struct media_folder, 1);
1827 folder->item = item;
1829 item->folder_type = type;
1831 if (mp->folder != NULL)
1834 mp->folder = folder;
1838 folder->parent = mp->folder;
1839 mp->folder->subfolders = g_slist_prepend(
1840 mp->folder->subfolders,
1843 mp->folders = g_slist_prepend(mp->folders, folder);
1848 void media_player_set_callbacks(struct media_player *mp,
1849 const struct media_player_callback *cbs,
1852 struct player_callback *cb;
1857 cb = g_new0(struct player_callback, 1);
1859 cb->user_data = user_data;
1864 struct media_item *media_player_set_playlist_item(struct media_player *mp,
1867 struct media_folder *folder = mp->playlist;
1868 struct media_item *item;
1870 DBG("%" PRIu64 "", uid);
1872 if (folder == NULL || uid == 0)
1875 item = media_folder_create_item(mp, folder, NULL,
1876 PLAYER_ITEM_TYPE_AUDIO, uid);
1880 media_item_set_playable(item, true);
1882 if (mp->track != item->metadata) {
1883 g_hash_table_unref(mp->track);
1884 mp->track = g_hash_table_ref(item->metadata);
1887 if (item == g_hash_table_lookup(mp->track, "Item"))
1890 if (mp->process_id == 0) {
1891 g_hash_table_remove_all(mp->track);
1892 mp->process_id = g_idle_add(process_metadata_changed, mp);
1895 g_hash_table_replace(mp->track, g_strdup("Item"),
1896 g_strdup(item->path));