/src/adhd/cras/src/server/cras_bt_player.c
Line | Count | Source |
1 | | /* Copyright 2016 The ChromiumOS Authors |
2 | | * Use of this source code is governed by a BSD-style license that can be |
3 | | * found in the LICENSE file. |
4 | | */ |
5 | | #include "cras/src/server/cras_bt_player.h" |
6 | | |
7 | | #include <dbus/dbus.h> |
8 | | #include <errno.h> |
9 | | #include <stdbool.h> |
10 | | #include <stdio.h> |
11 | | #include <stdlib.h> |
12 | | #include <string.h> |
13 | | #include <strings.h> |
14 | | #include <syslog.h> |
15 | | |
16 | | #include "cras/src/server/cras_bt_adapter.h" |
17 | | #include "cras/src/server/cras_bt_constants.h" |
18 | | #include "cras/src/server/cras_dbus_util.h" |
19 | | #include "cras/src/server/cras_utf8.h" |
20 | | #include "third_party/strlcpy/strlcpy.h" |
21 | | |
22 | | /* Object to hold current metadata. This is not a full list of what BlueZ/MPRIS |
23 | | * supports but a subset because Chromium only provides the following. |
24 | | */ |
25 | | struct cras_bt_player_metadata { |
26 | | char title[CRAS_PLAYER_METADATA_SIZE_MAX]; |
27 | | char artist[CRAS_PLAYER_METADATA_SIZE_MAX]; |
28 | | char album[CRAS_PLAYER_METADATA_SIZE_MAX]; |
29 | | int64_t length; |
30 | | }; |
31 | | |
32 | | /* Object to register as media player so that bluetoothd will report hardware |
33 | | * volume from device through bt_transport. Properties of the player are defined |
34 | | * in BlueZ's media API. |
35 | | */ |
36 | | struct cras_bt_player { |
37 | | const char* object_path; |
38 | | char* playback_status; |
39 | | char* identity; |
40 | | const char* loop_status; |
41 | | struct cras_bt_player_metadata* metadata; |
42 | | int64_t position; |
43 | | bool can_go_next; |
44 | | bool can_go_prev; |
45 | | bool can_play; |
46 | | bool can_pause; |
47 | | bool can_control; |
48 | | bool shuffle; |
49 | | void (*message_cb)(const char* message); |
50 | | }; |
51 | | |
52 | | static void cras_bt_on_player_registered(DBusPendingCall* pending_call, |
53 | 0 | void* data) { |
54 | 0 | DBusMessage* reply; |
55 | |
|
56 | 0 | reply = dbus_pending_call_steal_reply(pending_call); |
57 | 0 | dbus_pending_call_unref(pending_call); |
58 | |
|
59 | 0 | if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
60 | 0 | syslog(LOG_WARNING, "RegisterPlayer returned error: %s", |
61 | 0 | dbus_message_get_error_name(reply)); |
62 | 0 | dbus_message_unref(reply); |
63 | 0 | return; |
64 | 0 | } |
65 | | |
66 | 0 | dbus_message_unref(reply); |
67 | 0 | } |
68 | | |
69 | | /* Note that player properties will be used mostly for AVRCP qualification and |
70 | | * not for normal use cases. The corresponding media events won't be routed by |
71 | | * CRAS until we have a plan to provide general system API to handle media |
72 | | * control. |
73 | | */ |
74 | | static struct cras_bt_player player = { |
75 | | .object_path = CRAS_DEFAULT_PLAYER, |
76 | | .playback_status = NULL, |
77 | | .identity = NULL, |
78 | | .loop_status = "None", |
79 | | .shuffle = 0, |
80 | | .metadata = NULL, |
81 | | .position = 0, |
82 | | .can_go_next = 0, |
83 | | .can_go_prev = 0, |
84 | | .can_play = 0, |
85 | | .can_pause = 0, |
86 | | .can_control = 0, |
87 | | .message_cb = NULL, |
88 | | }; |
89 | | |
90 | | int cras_bt_register_player(DBusConnection* conn, |
91 | 0 | const struct cras_bt_adapter* adapter) { |
92 | 0 | const char* adapter_path; |
93 | 0 | DBusMessage* method_call; |
94 | 0 | DBusMessageIter message_iter, dict; |
95 | 0 | DBusPendingCall* pending_call; |
96 | |
|
97 | 0 | adapter_path = cras_bt_adapter_object_path(adapter); |
98 | 0 | method_call = dbus_message_new_method_call( |
99 | 0 | BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "RegisterPlayer"); |
100 | 0 | if (!method_call) { |
101 | 0 | return -ENOMEM; |
102 | 0 | } |
103 | | |
104 | 0 | dbus_message_iter_init_append(method_call, &message_iter); |
105 | 0 | dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, |
106 | 0 | &player.object_path); |
107 | |
|
108 | 0 | dbus_message_iter_open_container( |
109 | 0 | &message_iter, DBUS_TYPE_ARRAY, |
110 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
111 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
112 | 0 | &dict); |
113 | |
|
114 | 0 | append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING, |
115 | 0 | DBUS_TYPE_STRING_AS_STRING, &player.playback_status); |
116 | 0 | append_key_value(&dict, "Identity", DBUS_TYPE_STRING, |
117 | 0 | DBUS_TYPE_STRING_AS_STRING, &player.identity); |
118 | 0 | append_key_value(&dict, "LoopStatus", DBUS_TYPE_STRING, |
119 | 0 | DBUS_TYPE_STRING_AS_STRING, &player.loop_status); |
120 | 0 | append_key_value(&dict, "Position", DBUS_TYPE_INT64, |
121 | 0 | DBUS_TYPE_INT64_AS_STRING, &player.position); |
122 | 0 | append_key_value(&dict, "Shuffle", DBUS_TYPE_BOOLEAN, |
123 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.shuffle); |
124 | 0 | append_key_value(&dict, "CanGoNext", DBUS_TYPE_BOOLEAN, |
125 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_go_next); |
126 | 0 | append_key_value(&dict, "CanGoPrevious", DBUS_TYPE_BOOLEAN, |
127 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_go_prev); |
128 | 0 | append_key_value(&dict, "CanPlay", DBUS_TYPE_BOOLEAN, |
129 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_play); |
130 | 0 | append_key_value(&dict, "CanPause", DBUS_TYPE_BOOLEAN, |
131 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_pause); |
132 | 0 | append_key_value(&dict, "CanControl", DBUS_TYPE_BOOLEAN, |
133 | 0 | DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_control); |
134 | |
|
135 | 0 | dbus_message_iter_close_container(&message_iter, &dict); |
136 | |
|
137 | 0 | if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, |
138 | 0 | DBUS_TIMEOUT_USE_DEFAULT)) { |
139 | 0 | dbus_message_unref(method_call); |
140 | 0 | return -ENOMEM; |
141 | 0 | } |
142 | | |
143 | 0 | dbus_message_unref(method_call); |
144 | 0 | if (!pending_call) { |
145 | 0 | return -EIO; |
146 | 0 | } |
147 | | |
148 | 0 | if (!dbus_pending_call_set_notify(pending_call, cras_bt_on_player_registered, |
149 | 0 | &player, NULL)) { |
150 | 0 | dbus_pending_call_cancel(pending_call); |
151 | 0 | dbus_pending_call_unref(pending_call); |
152 | 0 | return -ENOMEM; |
153 | 0 | } |
154 | 0 | return 0; |
155 | 0 | } |
156 | | |
157 | | static void cras_bt_on_player_unregistered(DBusPendingCall* pending_call, |
158 | 0 | void* data) { |
159 | 0 | DBusMessage* reply; |
160 | |
|
161 | 0 | reply = dbus_pending_call_steal_reply(pending_call); |
162 | 0 | dbus_pending_call_unref(pending_call); |
163 | |
|
164 | 0 | if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
165 | 0 | syslog(LOG_WARNING, "UnregisterPlayer returned error: %s", |
166 | 0 | dbus_message_get_error_name(reply)); |
167 | 0 | } |
168 | |
|
169 | 0 | dbus_message_unref(reply); |
170 | 0 | } |
171 | | int cras_bt_unregister_player(DBusConnection* conn, |
172 | 0 | const struct cras_bt_adapter* adapter) { |
173 | 0 | const char* adapter_path; |
174 | 0 | DBusMessage* method_call; |
175 | 0 | DBusPendingCall* pending_call; |
176 | |
|
177 | 0 | adapter_path = cras_bt_adapter_object_path(adapter); |
178 | 0 | method_call = dbus_message_new_method_call( |
179 | 0 | BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "UnregisterPlayer"); |
180 | 0 | if (!method_call) { |
181 | 0 | return -ENOMEM; |
182 | 0 | } |
183 | | |
184 | 0 | if (!dbus_message_append_args(method_call, DBUS_TYPE_OBJECT_PATH, |
185 | 0 | &player.object_path, DBUS_TYPE_INVALID)) { |
186 | 0 | dbus_message_unref(method_call); |
187 | 0 | return -ENOMEM; |
188 | 0 | } |
189 | | |
190 | 0 | if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, |
191 | 0 | DBUS_TIMEOUT_USE_DEFAULT)) { |
192 | 0 | dbus_message_unref(method_call); |
193 | 0 | return -ENOMEM; |
194 | 0 | } |
195 | | |
196 | 0 | dbus_message_unref(method_call); |
197 | 0 | if (!pending_call) { |
198 | 0 | return -EIO; |
199 | 0 | } |
200 | | |
201 | 0 | if (!dbus_pending_call_set_notify( |
202 | 0 | pending_call, cras_bt_on_player_unregistered, &player, NULL)) { |
203 | 0 | dbus_pending_call_cancel(pending_call); |
204 | 0 | dbus_pending_call_unref(pending_call); |
205 | 0 | return -ENOMEM; |
206 | 0 | } |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | | static DBusHandlerResult cras_bt_player_handle_message(DBusConnection* conn, |
211 | | DBusMessage* message, |
212 | 0 | void* arg) { |
213 | 0 | const char* msg = dbus_message_get_member(message); |
214 | |
|
215 | 0 | if (player.message_cb) { |
216 | 0 | player.message_cb(msg); |
217 | 0 | } |
218 | |
|
219 | 0 | return DBUS_HANDLER_RESULT_HANDLED; |
220 | 0 | } |
221 | | |
222 | 0 | static int cras_bt_player_init() { |
223 | 0 | player.playback_status = calloc(1, CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX); |
224 | 0 | if (!player.playback_status) { |
225 | 0 | return -ENOMEM; |
226 | 0 | } |
227 | | |
228 | 0 | player.identity = calloc(1, CRAS_PLAYER_IDENTITY_SIZE_MAX); |
229 | 0 | if (!player.identity) { |
230 | 0 | goto nomem; |
231 | 0 | } |
232 | | |
233 | 0 | strlcpy(player.playback_status, CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT, |
234 | 0 | CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX); |
235 | 0 | strlcpy(player.identity, CRAS_PLAYER_IDENTITY_DEFAULT, |
236 | 0 | CRAS_PLAYER_IDENTITY_SIZE_MAX); |
237 | 0 | player.position = 0; |
238 | |
|
239 | 0 | player.metadata = calloc(1, sizeof(*(player.metadata))); |
240 | 0 | if (!player.metadata) { |
241 | 0 | goto nomem; |
242 | 0 | } |
243 | 0 | return 0; |
244 | 0 | nomem: |
245 | 0 | free(player.playback_status); |
246 | 0 | free(player.identity); |
247 | 0 | return -ENOMEM; |
248 | 0 | } |
249 | | |
250 | | static void cras_bt_player_append_metadata_artist(DBusMessageIter* iter, |
251 | 0 | const char* artist) { |
252 | 0 | DBusMessageIter dict, variant, array; |
253 | 0 | const char* artist_key = "xesam:artist"; |
254 | |
|
255 | 0 | dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict); |
256 | 0 | dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &artist_key); |
257 | 0 | dbus_message_iter_open_container( |
258 | 0 | &dict, DBUS_TYPE_VARIANT, |
259 | 0 | DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, &variant); |
260 | 0 | dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, |
261 | 0 | DBUS_TYPE_STRING_AS_STRING, &array); |
262 | 0 | dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &artist); |
263 | 0 | dbus_message_iter_close_container(&variant, &array); |
264 | 0 | dbus_message_iter_close_container(&dict, &variant); |
265 | 0 | dbus_message_iter_close_container(iter, &dict); |
266 | 0 | } |
267 | | |
268 | | static void cras_bt_player_append_metadata(DBusMessageIter* iter, |
269 | | const char* title, |
270 | | const char* artist, |
271 | | const char* album, |
272 | 0 | dbus_int64_t length) { |
273 | 0 | DBusMessageIter variant, array; |
274 | 0 | dbus_message_iter_open_container( |
275 | 0 | iter, DBUS_TYPE_VARIANT, |
276 | 0 | DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
277 | 0 | DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING |
278 | 0 | DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
279 | 0 | &variant); |
280 | 0 | dbus_message_iter_open_container( |
281 | 0 | &variant, DBUS_TYPE_ARRAY, |
282 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
283 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
284 | 0 | &array); |
285 | 0 | if (!is_utf8_string(title)) { |
286 | 0 | syslog(LOG_DEBUG, "Non-utf8 title: %s", title); |
287 | 0 | title = ""; |
288 | 0 | } |
289 | 0 | if (!is_utf8_string(album)) { |
290 | 0 | syslog(LOG_DEBUG, "Non-utf8 album: %s", album); |
291 | 0 | album = ""; |
292 | 0 | } |
293 | 0 | if (!is_utf8_string(artist)) { |
294 | 0 | syslog(LOG_DEBUG, "Non-utf8 artist: %s", artist); |
295 | 0 | artist = ""; |
296 | 0 | } |
297 | |
|
298 | 0 | append_key_value(&array, "xesam:title", DBUS_TYPE_STRING, |
299 | 0 | DBUS_TYPE_STRING_AS_STRING, &title); |
300 | 0 | append_key_value(&array, "xesam:album", DBUS_TYPE_STRING, |
301 | 0 | DBUS_TYPE_STRING_AS_STRING, &album); |
302 | 0 | append_key_value(&array, "mpris:length", DBUS_TYPE_INT64, |
303 | 0 | DBUS_TYPE_INT64_AS_STRING, &length); |
304 | 0 | cras_bt_player_append_metadata_artist(&array, artist); |
305 | |
|
306 | 0 | dbus_message_iter_close_container(&variant, &array); |
307 | 0 | dbus_message_iter_close_container(iter, &variant); |
308 | 0 | } |
309 | | |
310 | | static bool cras_bt_player_parse_metadata(const char* title, |
311 | | const char* album, |
312 | | const char* artist, |
313 | 0 | const dbus_int64_t length) { |
314 | 0 | bool require_update = false; |
315 | |
|
316 | 0 | if (title && strcmp(player.metadata->title, title)) { |
317 | 0 | snprintf(player.metadata->title, CRAS_PLAYER_METADATA_SIZE_MAX, "%s", |
318 | 0 | title); |
319 | 0 | require_update = true; |
320 | 0 | } |
321 | 0 | if (artist && strcmp(player.metadata->artist, artist)) { |
322 | 0 | snprintf(player.metadata->artist, CRAS_PLAYER_METADATA_SIZE_MAX, "%s", |
323 | 0 | artist); |
324 | 0 | require_update = true; |
325 | 0 | } |
326 | 0 | if (album && strcmp(player.metadata->album, album)) { |
327 | 0 | snprintf(player.metadata->album, CRAS_PLAYER_METADATA_SIZE_MAX, "%s", |
328 | 0 | album); |
329 | 0 | require_update = true; |
330 | 0 | } |
331 | 0 | if (length && player.metadata->length != length) { |
332 | 0 | player.metadata->length = length; |
333 | 0 | require_update = true; |
334 | 0 | } |
335 | |
|
336 | 0 | return require_update; |
337 | 0 | } |
338 | | |
339 | 0 | int cras_bt_player_create(DBusConnection* conn) { |
340 | 0 | static const DBusObjectPathVTable player_vtable = { |
341 | 0 | .message_function = cras_bt_player_handle_message}; |
342 | |
|
343 | 0 | DBusError dbus_error; |
344 | 0 | struct cras_bt_adapter** adapters; |
345 | 0 | size_t num_adapters, i; |
346 | 0 | int ret; |
347 | |
|
348 | 0 | ret = cras_bt_player_init(); |
349 | 0 | if (ret < 0) { |
350 | 0 | return ret; |
351 | 0 | } |
352 | | |
353 | 0 | dbus_error_init(&dbus_error); |
354 | |
|
355 | 0 | if (!dbus_connection_register_object_path(conn, player.object_path, |
356 | 0 | &player_vtable, &dbus_error)) { |
357 | 0 | syslog(LOG_WARNING, "Cannot register player %s", player.object_path); |
358 | 0 | dbus_error_free(&dbus_error); |
359 | 0 | return -ENOMEM; |
360 | 0 | } |
361 | | |
362 | 0 | num_adapters = cras_bt_adapter_get_list(&adapters); |
363 | 0 | for (i = 0; i < num_adapters; ++i) { |
364 | 0 | cras_bt_register_player(conn, adapters[i]); |
365 | 0 | } |
366 | 0 | free(adapters); |
367 | 0 | return 0; |
368 | 0 | } |
369 | | |
370 | 0 | int cras_bt_player_destroy(DBusConnection* conn) { |
371 | 0 | struct cras_bt_adapter** adapters; |
372 | 0 | size_t num_adapters, i; |
373 | |
|
374 | 0 | num_adapters = cras_bt_adapter_get_list(&adapters); |
375 | |
|
376 | 0 | for (i = 0; i < num_adapters; ++i) { |
377 | 0 | cras_bt_unregister_player(conn, adapters[i]); |
378 | 0 | } |
379 | 0 | free(adapters); |
380 | |
|
381 | 0 | return dbus_connection_unregister_object_path(conn, player.object_path); |
382 | 0 | } |
383 | | |
384 | | int cras_bt_player_update_playback_status(DBusConnection* conn, |
385 | 0 | const char* status) { |
386 | 0 | DBusMessage* msg; |
387 | 0 | DBusMessageIter iter, dict; |
388 | 0 | const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER; |
389 | |
|
390 | 0 | if (!player.playback_status) { |
391 | 0 | return -ENXIO; |
392 | 0 | } |
393 | | |
394 | | /* Verify the string value matches one of the possible status defined in |
395 | | * bluez/profiles/audio/avrcp.c |
396 | | */ |
397 | 0 | if (strcasecmp(status, "stopped") != 0 && |
398 | 0 | strcasecmp(status, "playing") != 0 && strcasecmp(status, "paused") != 0 && |
399 | 0 | strcasecmp(status, "forward-seek") != 0 && |
400 | 0 | strcasecmp(status, "reverse-seek") != 0 && |
401 | 0 | strcasecmp(status, "error") != 0) { |
402 | 0 | return -EINVAL; |
403 | 0 | } |
404 | | |
405 | 0 | if (!strcasecmp(player.playback_status, status)) { |
406 | 0 | return 0; |
407 | 0 | } |
408 | | |
409 | 0 | strlcpy(player.playback_status, status, CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX); |
410 | |
|
411 | 0 | msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES, |
412 | 0 | "PropertiesChanged"); |
413 | 0 | if (!msg) { |
414 | 0 | return -ENOMEM; |
415 | 0 | } |
416 | | |
417 | 0 | dbus_message_iter_init_append(msg, &iter); |
418 | 0 | dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface); |
419 | 0 | dbus_message_iter_open_container( |
420 | 0 | &iter, DBUS_TYPE_ARRAY, |
421 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
422 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
423 | 0 | &dict); |
424 | 0 | append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING, |
425 | 0 | DBUS_TYPE_STRING_AS_STRING, &status); |
426 | 0 | dbus_message_iter_close_container(&iter, &dict); |
427 | |
|
428 | 0 | if (!dbus_connection_send(conn, msg, NULL)) { |
429 | 0 | dbus_message_unref(msg); |
430 | 0 | return -ENOMEM; |
431 | 0 | } |
432 | | |
433 | 0 | dbus_message_unref(msg); |
434 | 0 | return 0; |
435 | 0 | } |
436 | | |
437 | 0 | int cras_bt_player_update_identity(DBusConnection* conn, const char* identity) { |
438 | 0 | DBusMessage* msg; |
439 | 0 | DBusMessageIter iter, dict; |
440 | 0 | const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER; |
441 | |
|
442 | 0 | if (!player.identity) { |
443 | 0 | return -ENXIO; |
444 | 0 | } |
445 | | |
446 | 0 | if (!identity) { |
447 | 0 | return -EINVAL; |
448 | 0 | } |
449 | | |
450 | 0 | if (strnlen(identity, CRAS_PLAYER_IDENTITY_SIZE_MAX - 1) == |
451 | 0 | CRAS_PLAYER_IDENTITY_SIZE_MAX - 1) { |
452 | 0 | syslog(LOG_WARNING, "Identity is too long, using default"); |
453 | 0 | identity = CRAS_PLAYER_IDENTITY_DEFAULT; |
454 | 0 | } |
455 | |
|
456 | 0 | if (!is_utf8_string(identity)) { |
457 | 0 | syslog(LOG_DEBUG, "Non-utf8 identity: %s", identity); |
458 | 0 | identity = ""; |
459 | 0 | } |
460 | |
|
461 | 0 | if (!strcasecmp(player.identity, identity)) { |
462 | 0 | return 0; |
463 | 0 | } |
464 | | |
465 | 0 | strlcpy(player.identity, identity, CRAS_PLAYER_IDENTITY_SIZE_MAX); |
466 | |
|
467 | 0 | msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES, |
468 | 0 | "PropertiesChanged"); |
469 | 0 | if (!msg) { |
470 | 0 | return -ENOMEM; |
471 | 0 | } |
472 | | |
473 | 0 | dbus_message_iter_init_append(msg, &iter); |
474 | 0 | dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface); |
475 | 0 | dbus_message_iter_open_container( |
476 | 0 | &iter, DBUS_TYPE_ARRAY, |
477 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
478 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
479 | 0 | &dict); |
480 | 0 | append_key_value(&dict, "Identity", DBUS_TYPE_STRING, |
481 | 0 | DBUS_TYPE_STRING_AS_STRING, &identity); |
482 | 0 | dbus_message_iter_close_container(&iter, &dict); |
483 | |
|
484 | 0 | if (!dbus_connection_send(conn, msg, NULL)) { |
485 | 0 | dbus_message_unref(msg); |
486 | 0 | return -ENOMEM; |
487 | 0 | } |
488 | | |
489 | 0 | dbus_message_unref(msg); |
490 | 0 | return 0; |
491 | 0 | } |
492 | | |
493 | | int cras_bt_player_update_position(DBusConnection* conn, |
494 | 0 | const dbus_int64_t position) { |
495 | 0 | DBusMessage* msg; |
496 | 0 | DBusMessageIter iter, dict; |
497 | 0 | const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER; |
498 | |
|
499 | 0 | if (position < 0) { |
500 | 0 | return -EINVAL; |
501 | 0 | } |
502 | | |
503 | 0 | player.position = position; |
504 | |
|
505 | 0 | msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES, |
506 | 0 | "PropertiesChanged"); |
507 | 0 | if (!msg) { |
508 | 0 | return -ENOMEM; |
509 | 0 | } |
510 | | |
511 | 0 | dbus_message_iter_init_append(msg, &iter); |
512 | 0 | dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface); |
513 | 0 | dbus_message_iter_open_container( |
514 | 0 | &iter, DBUS_TYPE_ARRAY, |
515 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
516 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
517 | 0 | &dict); |
518 | 0 | append_key_value(&dict, "Position", DBUS_TYPE_INT64, |
519 | 0 | DBUS_TYPE_INT64_AS_STRING, &player.position); |
520 | 0 | dbus_message_iter_close_container(&iter, &dict); |
521 | |
|
522 | 0 | if (!dbus_connection_send(conn, msg, NULL)) { |
523 | 0 | dbus_message_unref(msg); |
524 | 0 | return -ENOMEM; |
525 | 0 | } |
526 | | |
527 | 0 | dbus_message_unref(msg); |
528 | 0 | return 0; |
529 | 0 | } |
530 | | |
531 | | int cras_bt_player_update_metadata(DBusConnection* conn, |
532 | | const char* title, |
533 | | const char* artist, |
534 | | const char* album, |
535 | 0 | const dbus_int64_t length) { |
536 | 0 | DBusMessage* msg; |
537 | 0 | DBusMessageIter iter, array, dict; |
538 | 0 | const char* property = "Metadata"; |
539 | 0 | const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER; |
540 | |
|
541 | 0 | if (!player.metadata) { |
542 | 0 | return -ENXIO; |
543 | 0 | } |
544 | | |
545 | 0 | msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES, |
546 | 0 | "PropertiesChanged"); |
547 | 0 | if (!msg) { |
548 | 0 | return -ENOMEM; |
549 | 0 | } |
550 | | |
551 | 0 | dbus_message_iter_init_append(msg, &iter); |
552 | 0 | dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface); |
553 | 0 | dbus_message_iter_open_container( |
554 | 0 | &iter, DBUS_TYPE_ARRAY, |
555 | 0 | DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING |
556 | 0 | DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
557 | 0 | &array); |
558 | 0 | dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); |
559 | 0 | dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property); |
560 | |
|
561 | 0 | if (!cras_bt_player_parse_metadata(title, album, artist, length)) { |
562 | | // Nothing to update. |
563 | 0 | dbus_message_unref(msg); |
564 | 0 | return 0; |
565 | 0 | } |
566 | | |
567 | 0 | cras_bt_player_append_metadata( |
568 | 0 | &dict, player.metadata->title, player.metadata->artist, |
569 | 0 | player.metadata->album, player.metadata->length); |
570 | |
|
571 | 0 | dbus_message_iter_close_container(&array, &dict); |
572 | 0 | dbus_message_iter_close_container(&iter, &array); |
573 | |
|
574 | 0 | if (!dbus_connection_send(conn, msg, NULL)) { |
575 | 0 | dbus_message_unref(msg); |
576 | 0 | return -ENOMEM; |
577 | 0 | } |
578 | | |
579 | 0 | dbus_message_unref(msg); |
580 | 0 | return 0; |
581 | 0 | } |