]> git.ipfire.org Git - thirdparty/shairport-sync.git/blame - dacp.c
Update check_classic_systemd_full.yml
[thirdparty/shairport-sync.git] / dacp.c
CommitLineData
3198ec42
MB
1/*
2 * DACP protocol handler. This file is part of Shairport Sync.
a6acb606 3 * Copyright (c) Mike Brady 2017 -- 2020
3198ec42
MB
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without
9 * restriction, including without limitation the rights to use,
10 * copy, modify, merge, publish, distribute, sublicense, and/or
11 * sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
3198ec42
MB
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
24 */
25
6af24f7c
MB
26// Information about the four-character codes is from many sources, with thanks, including
27// https://github.com/melloware/dacp-net/blob/master/Melloware.DACP/
28
3198ec42
MB
29#include "dacp.h"
30#include "common.h"
31#include "config.h"
32
33#include <arpa/inet.h>
34#include <errno.h>
c36a7822 35#include <inttypes.h>
3198ec42
MB
36#include <math.h>
37#include <memory.h>
38#include <netdb.h>
39#include <netinet/in.h>
fe3b70b4 40#include <pthread.h>
3198ec42 41#include <stdlib.h>
413df6d0 42#include <sys/time.h>
c8b0be30 43#include <time.h>
3198ec42
MB
44#include <unistd.h>
45
0801290a 46#include "metadata_hub.h"
1637a79d
MB
47#include "tinyhttp/http.h"
48
0f68ff0c 49typedef struct {
d692fab9
MB
50 int players_connection_thread_index; // the connection thread index when a player thread is
51 // associated with this, zero otherwise
a6acb606 52 int scan_enable; // set to 1 if scanning should be considered
32c7a535 53 char dacp_id[256]; // the DACP ID string
d692fab9
MB
54 uint16_t port; // zero if no port discovered
55 short connection_family; // AF_INET6 or AF_INET
27e42a66 56 int always_use_revision_number_1; // for dealing with forked-daapd;
d692fab9
MB
57 uint32_t scope_id; // if it's an ipv6 connection, this will be its scope id
58 char ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
6857d06b 59 char *active_remote_id; // send this when you want to send remote control commands
32c7a535 60 void *port_monitor_private_storage;
88c55066
MB
61} dacp_server_record;
62
79e22063 63int dacp_monitor_initialised = 0;
fe3b70b4 64pthread_t dacp_monitor_thread;
88c55066 65dacp_server_record dacp_server;
72079ada 66void *mdns_dacp_monitor_private_storage_pointer;
fe3b70b4 67
1637a79d
MB
68// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
69struct HttpResponse {
70 void *body; // this will be a malloc'ed pointer
71 ssize_t malloced_size; // this will be its allocated size
72 ssize_t size; // the current size of the content
73 int code;
74};
75
69b3b5d7
MB
76void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size) {
77 void *t = realloc(ptr, size);
78 if ((t == NULL) && (size != 0))
79 debug(1, "Response realloc of size %d failed!", size);
80 return t;
8991f342 81}
1637a79d 82
69b3b5d7 83void response_body(void *opaque, const char *data, int size) {
1637a79d
MB
84 struct HttpResponse *response = (struct HttpResponse *)opaque;
85
6857d06b 86 ssize_t space_available = response->malloced_size - response->size;
1637a79d 87 if (space_available < size) {
6857d06b
MB
88 // debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n",
89 // size,
90 // size - space_available);
91 ssize_t size_requested = size - space_available + response->malloced_size + 16384;
1637a79d
MB
92 void *t = realloc(response->body, size_requested);
93 response->malloced_size = size_requested;
6857d06b 94 if (t)
1637a79d 95 response->body = t;
6857d06b 96 else {
57bcea52 97 die("dacp: can't allocate any more space for parser.");
1637a79d
MB
98 }
99 }
100 memcpy(response->body + response->size, data, size);
101 response->size += size;
102}
103
8991f342
MB
104static void
105response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey,
106 __attribute__((unused)) int nkey, __attribute__((unused)) const char *cvalue,
107 __attribute__((unused)) int nvalue) { /* example doesn't care about headers */
1637a79d
MB
108}
109
110static void response_code(void *opaque, int code) {
111 struct HttpResponse *response = (struct HttpResponse *)opaque;
112 response->code = code;
113}
114
115static const struct http_funcs responseFuncs = {
86b6d7bf
MB
116 response_realloc,
117 response_body,
118 response_header,
119 response_code,
1637a79d
MB
120};
121
7fb89135
MB
122// static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
123// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
124static pthread_mutex_t dacp_conversation_lock;
125static pthread_mutex_t dacp_server_information_lock;
a6acb606 126static pthread_cond_t dacp_server_information_cv;
fe3b70b4 127
5f750ba9 128void addrinfo_cleanup(void *arg) {
703ab8e5 129 // debug(1, "addrinfo cleanup called.");
5f750ba9
MB
130 struct addrinfo **info = (struct addrinfo **)arg;
131 freeaddrinfo(*info);
132}
133
134void mutex_lock_cleanup(void *arg) {
5f750ba9 135 pthread_mutex_t *m = (pthread_mutex_t *)arg;
8e72574a 136 if (pthread_mutex_unlock(m))
8f20e580 137 debug(1, "Error releasing mutex.");
5f750ba9
MB
138}
139
140void connect_cleanup(void *arg) {
5f750ba9 141 int *fd = (int *)arg;
668f7806 142 // debug(2, "dacp_send_command: close socket %d.",*fd);
5f750ba9
MB
143 close(*fd);
144}
145
146void http_cleanup(void *arg) {
703ab8e5 147 // debug(1, "http cleanup called.");
5f750ba9
MB
148 struct http_roundtripper *rt = (struct http_roundtripper *)arg;
149 http_free(rt);
150}
151
3e2c3e83 152int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
daab7a63 153 int result;
85b17cda
MB
154 // debug(1,"dacp_send_command: command is: \"%s\".",command);
155
156 if (dacp_server.port == 0) {
77a5cf1e 157 // debug(3, "No DACP port specified yet");
85b17cda
MB
158 result = 490; // no port specified
159 } else {
daab7a63
MB
160
161 // will malloc space for the body or set it to NULL -- the caller should free it.
162
163 // Using some custom HTTP-like return codes
164 // 498 Bad Address information for the DACP server
165 // 497 Can't establish a socket to the DACP server
166 // 496 Can't connect to the DACP server
167 // 495 Error receiving response
168 // 494 This client is already busy
169 // 493 Client failed to send a message
170 // 492 Argument out of range
171 // 491 Client refused connection
a6acb606 172 // 490 No port specified
daab7a63
MB
173
174 struct addrinfo hints, *res;
175 int sockfd;
176
177 struct HttpResponse response;
178 response.body = NULL;
179 response.malloced_size = 0;
180 response.size = 0;
181 response.code = 0;
182
183 char portstring[10], server[1024], message[1024];
184 memset(&portstring, 0, sizeof(portstring));
185 if (dacp_server.connection_family == AF_INET6) {
186 snprintf(server, sizeof(server), "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
187 } else {
188 strcpy(server, dacp_server.ip_string);
189 }
190 snprintf(portstring, sizeof(portstring), "%u", dacp_server.port);
1637a79d 191
daab7a63 192 // first, load up address structs with getaddrinfo():
0f68ff0c 193
daab7a63
MB
194 memset(&hints, 0, sizeof(hints));
195 hints.ai_family = AF_UNSPEC;
196 hints.ai_socktype = SOCK_STREAM;
0f68ff0c 197
daab7a63 198 // debug(1, "DACP port string is \"%s:%s\".", server, portstring);
0f68ff0c 199
daab7a63
MB
200 int ires = getaddrinfo(server, portstring, &hints, &res);
201 if (ires) {
202 // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
203 response.code = 498; // Bad Address information for the DACP server
204 } else {
67e9b1b6 205 uint64_t start_time = get_absolute_time_in_ns();
daab7a63
MB
206 pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
207 // only do this one at a time -- not sure it is necessary, but better safe than sorry
0f68ff0c 208
fd880056
MB
209 // int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command,
210 // 1);
2dc6af2d 211 int mutex_reply = debug_mutex_lock(&dacp_conversation_lock, 2000000, 1);
daab7a63
MB
212 // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
213 if (mutex_reply == 0) {
214 pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
0f68ff0c 215
daab7a63
MB
216 // make a socket:
217 sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
0f68ff0c 218
daab7a63 219 if (sockfd == -1) {
85b17cda
MB
220 // debug(1, "DACP socket could not be created -- error %d:
221 // \"%s\".",errno,strerror(errno));
daab7a63 222 response.code = 497; // Can't establish a socket to the DACP server
1637a79d 223 } else {
daab7a63
MB
224 pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
225 // debug(2, "dacp_send_command: open socket %d.",sockfd);
226
85b17cda
MB
227 // This is for limiting the time to be spent waiting for a response.
228
daab7a63
MB
229 struct timeval tv;
230 tv.tv_sec = 0;
6857d06b 231 tv.tv_usec = 500000;
27e42a66 232 if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
54d761ff 233 debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
daab7a63
MB
234 if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
235 debug(1, "dacp_send_command: error %d setting send timeout.", errno);
236
237 // connect!
238 // debug(1, "DACP socket created.");
239 if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
240 // debug(1, "dacp_send_command: connect failed with errno %d.", errno);
241 if (errno == ECONNREFUSED)
242 response.code = 491; // DACP server doesn't want to talk anymore...
243 else
244 response.code = 496; // Can't connect to the DACP server
6af24f7c 245 } else {
daab7a63
MB
246 // debug(1,"DACP connect succeeded.");
247
248 snprintf(message, sizeof(message),
6857d06b 249 "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %s\r\n\r\n",
85b17cda
MB
250 command, dacp_server.ip_string, dacp_server.port,
251 dacp_server.active_remote_id);
daab7a63
MB
252
253 // Send command
254 debug(3, "dacp_send_command: \"%s\".", command);
255 ssize_t wresp = send(sockfd, message, strlen(message), 0);
256 if (wresp == -1) {
257 char errorstring[1024];
258 strerror_r(errno, (char *)errorstring, sizeof(errorstring));
259 debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
260 struct linger so_linger;
261 so_linger.l_onoff = 1; // "true"
262 so_linger.l_linger = 0;
263 int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
264 if (err)
265 debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
266 }
267 if (wresp != (ssize_t)strlen(message)) {
268 // debug(1, "dacp_send_command: send failed.");
269 response.code = 493; // Client failed to send a message
6af24f7c 270
daab7a63
MB
271 } else {
272
6857d06b
MB
273 response.body = malloc(2048); // it can resize this if necessary
274 response.malloced_size = 2048;
daab7a63
MB
275 pthread_cleanup_push(malloc_cleanup, response.body);
276
277 struct http_roundtripper rt;
278 http_init(&rt, responseFuncs, &response);
279 pthread_cleanup_push(http_cleanup, &rt);
280
281 int needmore = 1;
282 int looperror = 0;
283 char buffer[8192];
284 memset(buffer, 0, sizeof(buffer));
285 while (needmore && !looperror) {
286 const char *data = buffer;
27e42a66
MB
287 if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
288 debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
daab7a63
MB
289 ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
290 // debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
291 if (ndata <= 0) {
292 if (ndata == -1) {
293 char errorstring[1024];
294 strerror_r(errno, (char *)errorstring, sizeof(errorstring));
295 debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
296 (char *)errorstring);
297 struct linger so_linger;
298 so_linger.l_onoff = 1; // "true"
299 so_linger.l_linger = 0;
85b17cda
MB
300 int err =
301 setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
daab7a63 302 if (err)
85b17cda
MB
303 debug(
304 1,
305 "Could not set the dacp socket to abort due to a read error on closing.");
daab7a63
MB
306 }
307
308 free(response.body);
309 response.body = NULL;
310 response.malloced_size = 0;
311 response.size = 0;
312 response.code = 495; // Error receiving response
313 looperror = 1;
8f20e580
MB
314 }
315
daab7a63
MB
316 while (needmore && ndata && !looperror) {
317 int read;
318 needmore = http_data(&rt, data, ndata, &read);
319 ndata -= read;
320 data += read;
321 }
322 }
323
324 if (http_iserror(&rt)) {
325 debug(3, "dacp_send_command: error parsing data.");
6af24f7c
MB
326 free(response.body);
327 response.body = NULL;
328 response.malloced_size = 0;
329 response.size = 0;
6af24f7c 330 }
daab7a63
MB
331 // debug(1,"Size of response body is %d",response.size);
332 pthread_cleanup_pop(1); // this should call http_cleanup
333 // http_free(&rt);
334 pthread_cleanup_pop(
335 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
6af24f7c 336 }
1637a79d 337 }
daab7a63
MB
338 pthread_cleanup_pop(1); // this should close the socket
339 // close(sockfd);
340 // debug(1,"DACP socket closed.");
6af24f7c 341 }
daab7a63
MB
342 pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
343 // pthread_mutex_unlock(&dacp_conversation_lock);
344 // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
345 // debug(1,"dacp_conversation_lock released.");
346 } else {
347 debug(3,
348 "dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
349 "when attempting to "
350 "send the command \"%s\". Possible timeout?",
351 command);
352 response.code = 494; // This client is already busy
0f68ff0c 353 }
daab7a63
MB
354 pthread_cleanup_pop(1); // this should free the addrinfo
355 // freeaddrinfo(res);
67e9b1b6 356 uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
daab7a63 357 debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
67e9b1b6 358 (1.0 * et) / 1000000000, response.code, command);
0f68ff0c 359 }
daab7a63
MB
360 *body = response.body;
361 *bodysize = response.size;
362 result = response.code;
71f62a2f 363 }
daab7a63 364 return result;
0f68ff0c
MB
365}
366
1637a79d
MB
367int send_simple_dacp_command(const char *command) {
368 int reply = 0;
369 char *server_reply = NULL;
d734b7e7 370 debug(2, "send_simple_dacp_command: sending command \"%s\".", command);
1637a79d
MB
371 ssize_t reply_size = 0;
372 reply = dacp_send_command(command, &server_reply, &reply_size);
373 if (server_reply) {
374 free(server_reply);
375 server_reply = NULL;
376 }
377 return reply;
378}
0f68ff0c 379
c02e53ea 380void relinquish_dacp_server_information(rtsp_conn_info *conn) {
d692fab9
MB
381 // this will set the dacp_server.players_connection_thread_index to zero iff it has the same value
382 // as the conn's connection number
383 // this is to signify that the player has stopped, but only if another thread (with a different
384 // index) hasn't already taken over the dacp service
2eaaa9cd 385 debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
c02e53ea
MB
386 if (dacp_server.players_connection_thread_index == conn->connection_number)
387 dacp_server.players_connection_thread_index = 0;
2eaaa9cd 388 debug_mutex_unlock(&dacp_server_information_lock, 3);
c02e53ea
MB
389}
390
fe3b70b4 391// this will be running on the thread of its caller, not of the conversation thread...
32c7a535
MB
392// tell the DACP conversation thread what DACP server we are listening to
393// if the active_remote_id is the same we don't change anything apart from
394// the conversation number
395// Thus, we can keep the DACP port that might have previously been discovered
396void set_dacp_server_information(rtsp_conn_info *conn) {
a9d6e0a2 397 // debug(1, "set_dacp_server_information");
2eaaa9cd 398 debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
c02e53ea 399 dacp_server.players_connection_thread_index = conn->connection_number;
9e944e40
MB
400
401 if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) {
9e944e40 402 if (conn->dacp_id)
c8b0be30 403 strncpy(dacp_server.dacp_id, conn->dacp_id, sizeof(dacp_server.dacp_id) - 1);
9e944e40
MB
404 else
405 dacp_server.dacp_id[0] = '\0';
32c7a535 406 dacp_server.port = 0;
c36a7822 407 dacp_server.scan_enable = 0;
32c7a535
MB
408 dacp_server.connection_family = conn->connection_ip_family;
409 dacp_server.scope_id = conn->self_scope_id;
410 strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN);
27e42a66 411 debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
4aab0a6f 412 dacp_server.ip_string, dacp_server.dacp_id);
32c7a535 413
b7d9c22d 414 /*
6acd73da 415
b7d9c22d
MB
416 "long polling" is not implemented by Shairport Sync, whereby by sending the client the
417 last-received revision number, the link will hang until a change occurs.
6acd73da 418
b7d9c22d 419 Instead, at present, Shairport Sync always uses a revision number of 1.
6acd73da 420
b7d9c22d 421 */
b29e92b8
MB
422
423 // always use revision number 1
424 dacp_server.always_use_revision_number_1 = 1;
27e42a66 425
32c7a535
MB
426 metadata_hub_modify_prolog();
427 int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
428 metadata_store.dacp_server_active = dacp_server.scan_enable;
429 if ((metadata_store.client_ip == NULL) ||
430 (strncmp(metadata_store.client_ip, conn->client_ip_string, INET6_ADDRSTRLEN) != 0)) {
431 if (metadata_store.client_ip)
432 free(metadata_store.client_ip);
433 metadata_store.client_ip = strndup(conn->client_ip_string, INET6_ADDRSTRLEN);
434 debug(3, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
435 ch = 1;
436 }
437 metadata_hub_modify_epilog(ch);
00664871
MB
438 } else {
439 if (dacp_server.port) {
a02b9759 440 // debug(1, "Re-enable scanning.");
00664871 441 dacp_server.scan_enable = 1;
4aab0a6f
MB
442 // metadata_hub_modify_prolog();
443 // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
444 // metadata_store.dacp_server_active = dacp_server.scan_enable;
445 // metadata_hub_modify_epilog(ch);
00664871 446 }
69b3b5d7 447 }
6857d06b 448 if (dacp_server.active_remote_id)
3fc95704 449 free(dacp_server.active_remote_id);
c27fb1c9 450 if (conn->dacp_active_remote)
18c9e13a
MB
451 dacp_server.active_remote_id =
452 strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
453 // the active remote will change.
c27fb1c9 454 else
18c9e13a 455 dacp_server.active_remote_id = NULL;
c27fb1c9 456
3fc95704 457 debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
fe3b70b4 458 pthread_cond_signal(&dacp_server_information_cv);
2eaaa9cd 459 debug_mutex_unlock(&dacp_server_information_lock, 3);
fe3b70b4
MB
460}
461
32c7a535 462void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
2eaaa9cd 463 debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
daab7a63 464 debug(2,
86b6d7bf
MB
465 "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
466 "number %d.",
72079ada 467 dacp_id, dacp_server.dacp_id, port);
e925e196 468 if ((dacp_id == NULL) || (dacp_server.dacp_id[0] == '\0')) {
3fc95704 469 warn("dacp_id or dacp_server.dacp_id NULL detected");
32c7a535 470 } else {
3fc95704
MB
471 if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
472 dacp_server.port = port;
473 if (port == 0)
474 dacp_server.scan_enable = 0;
475 else {
476 dacp_server.scan_enable = 1;
477 // debug(2, "dacp_monitor_port_update_callback enables scan");
478 }
479 // metadata_hub_modify_prolog();
480 // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
481 // metadata_store.dacp_server_active = dacp_server.scan_enable;
482 // metadata_hub_modify_epilog(ch);
483 } else {
dbebdca8 484 debug(2, "dacp port monitor reporting on an out-of-use remote.");
3fc95704 485 }
32c7a535 486 }
4aab0a6f 487 pthread_cond_signal(&dacp_server_information_cv);
2eaaa9cd 488 debug_mutex_unlock(&dacp_server_information_lock, 3);
32c7a535 489}
f1d45034 490
8991f342 491void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
88c55066 492 int scan_index = 0;
27e42a66 493 int always_use_revision_number_1 = 0;
73bf006c 494 // char server_reply[10000];
bef287d9 495 // debug(1, "DACP monitor thread started.");
fe3b70b4 496 // wait until we get a valid port number to begin monitoring it
1637a79d 497 int32_t revision_number = 1;
f9845d01
MB
498 int bad_result_count = 0;
499 int idle_scan_count = 0;
fe3b70b4 500 while (1) {
f9845d01 501 int result = 0;
f1d45034 502 int32_t the_volume;
2dc6af2d 503 pthread_cleanup_debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
fb3f9f51
MB
504 if (dacp_server.scan_enable == 0) {
505 metadata_hub_modify_prolog();
506 int ch = (metadata_store.dacp_server_active != 0) ||
507 (metadata_store.advanced_dacp_server_active != 0);
508 metadata_store.dacp_server_active = 0;
509 metadata_store.advanced_dacp_server_active = 0;
77a5cf1e 510 /*
54d761ff 511 debug(3,
85b17cda
MB
512 "setting metadata_store.dacp_server_active and "
513 "metadata_store.advanced_dacp_server_active to 0 with an update "
86b6d7bf 514 "flag value of %d",
fb3f9f51 515 ch);
77a5cf1e 516 */
fb3f9f51 517 metadata_hub_modify_epilog(ch);
a6acb606 518
54d761ff
MB
519 uint64_t time_to_wait_for_wakeup_ns =
520 (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
a6acb606
MB
521
522#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
56bef8e7 523 uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns;
a6acb606
MB
524 uint64_t sec = time_of_wakeup_ns / 1000000000;
525 uint64_t nsec = time_of_wakeup_ns % 1000000000;
526#endif
527#ifdef COMPILE_FOR_OSX
528 uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
529 uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
530#endif
531
532 struct timespec time_to_wait;
533 time_to_wait.tv_sec = sec;
534 time_to_wait.tv_nsec = nsec;
535
536 while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
537 // debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
538
539#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
540 result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
541 &time_to_wait); // this is a pthread cancellation point
54d761ff
MB
542 // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
543 // PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
a6acb606
MB
544
545#endif
546#ifdef COMPILE_FOR_OSX
54d761ff
MB
547 result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
548 &dacp_server_information_lock, &time_to_wait);
a6acb606
MB
549#endif
550 }
551 if (dacp_server.scan_enable == 1) {
552 bad_result_count = 0;
553 idle_scan_count = 0;
fb3f9f51 554 }
88c55066 555 }
85b17cda 556
54d761ff
MB
557 always_use_revision_number_1 =
558 dacp_server.always_use_revision_number_1; // set this while access is locked
27e42a66 559
cbbe84d8 560 result = dacp_get_volume(&the_volume); // just want the http code
daab7a63 561 pthread_cleanup_pop(1);
4aab0a6f 562
54d761ff 563 if (result == 490) { // 490 means no port was specified
ea4eafbf 564 if (strlen(dacp_server.dacp_id) != 0) {
a6acb606
MB
565 // debug(1,"mdns_dacp_monitor_set_id");
566 mdns_dacp_monitor_set_id(dacp_server.dacp_id);
567 }
daab7a63 568 } else {
a6acb606
MB
569 scan_index++;
570 // debug(1,"DACP Scan Result: %d.", result);
85b17cda 571
a6acb606
MB
572 if ((result == 200) || (result == 400)) {
573 bad_result_count = 0;
574 } else {
575 if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
576 bad_result_count++;
577 }
85b17cda 578
a6acb606
MB
579 // here, do the debouncing calculations to see
580 // if the dacp server and the
581 // advanced dacp server are available
582
583 // -1 means we don't know because some bad statuses have been reported
584 // 0 means definitely no
585 // +1 means definitely yes
586
587 int dacp_server_status_now = -1;
588 int advanced_dacp_server_status_now = -1;
589
590 if (bad_result_count == 0) {
591 dacp_server_status_now = 1;
592 if (result == 200)
593 advanced_dacp_server_status_now = 1;
594 else if (result == 400)
595 advanced_dacp_server_status_now = 0;
596 } else if (bad_result_count ==
597 config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
598 // then it's gone
599 dacp_server_status_now = 0;
2b980ff2 600 advanced_dacp_server_status_now = 0;
a6acb606 601 }
4aab0a6f 602
a6acb606
MB
603 if (metadata_store.player_thread_active == 0)
604 idle_scan_count++;
605 else
606 idle_scan_count = 0;
f9845d01 607
54d761ff
MB
608 debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
609 bad_result_count, idle_scan_count);
4aab0a6f 610
a6acb606
MB
611 /* not used
612 // decide if idle for too long
613 if (idle_scan_count == config.scan_max_inactive_count) {
614 debug(2, "DACP server status scanning stopped.");
615 dacp_server.scan_enable = 0;
616 }
617 */
85b17cda 618
a6acb606
MB
619 int update_needed = 0;
620 metadata_hub_modify_prolog();
621 if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
622 if (metadata_store.dacp_server_active != dacp_server_status_now) {
623 debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
624 metadata_store.dacp_server_active = dacp_server_status_now;
625 update_needed = 1;
626 }
daab7a63 627 }
a6acb606
MB
628 if (advanced_dacp_server_status_now !=
629 -1) { // if advanced_dacp_server_status_now is actually known...
630 if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
631 debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
632 metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
633 update_needed = 1;
634 }
daab7a63 635 }
f1d45034 636
a6acb606 637 metadata_hub_modify_epilog(update_needed);
85b17cda 638
a6acb606
MB
639 // pthread_mutex_unlock(&dacp_server_information_lock);
640 // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
641 // dacp_server.ip_string, dacp_server.port, scan_index);
6dc30c6c 642
cbbe84d8 643 if (result == 200) {
a6acb606
MB
644 metadata_hub_modify_prolog();
645 int diff = metadata_store.speaker_volume != the_volume;
646 if (diff)
647 metadata_store.speaker_volume = the_volume;
648 metadata_hub_modify_epilog(diff);
649
650 ssize_t le;
651 char *response = NULL;
652 int32_t item_size;
653 char command[1024] = "";
6acd73da 654 if (always_use_revision_number_1 != 0) // see the "long polling" note above
a6acb606
MB
655 revision_number = 1;
656 snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
657 revision_number);
658 // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
659 result = dacp_send_command(command, &response, &le);
660 // debug(1,"Response to \"%s\" is %d.",command,result);
661 // remember: unless the revision_number you pass in is 1,
662 // response will be 200 only if there's something new to report.
663 if (result == 200) {
664 // if (0) {
665 char *sp = response;
666 if (le >= 8) {
667 // here start looking for the contents of the status update
668 if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
669 // here, we know that we are receiving playerstatusupdates, so set a flag
670 metadata_hub_modify_prolog();
671 // debug(1, "playstatusupdate release track metadata");
672 // metadata_hub_reset_track_metadata();
673 // metadata_store.playerstatusupdates_are_received = 1;
674 sp -= item_size; // drop down into the array -- don't skip over it
675 le -= 8;
676 // char typestring[5];
677 // we need to acquire the metadata data structure and possibly update it
678 while (le >= 8) {
679 uint32_t type = dacp_tlv_crawl(&sp, &item_size);
680 le -= item_size + 8;
681 char *t;
682 // char u;
683 // char *st;
684 int32_t r;
685 uint32_t ui;
686 // uint64_t v;
687 // int i;
688
689 switch (type) {
690 case 'cmsr': // revision number
691 t = sp - item_size;
692 revision_number = ntohl(*(uint32_t *)(t));
693 // debug(1,"New revision number received: %d", revision_number);
3a4e29c5 694 break;
a6acb606
MB
695 case 'caps': // play status
696 t = sp - item_size;
697 r = *(unsigned char *)(t);
698 switch (r) {
699 case 2:
700 if (metadata_store.play_status != PS_STOPPED) {
701 metadata_store.play_status = PS_STOPPED;
702 debug(2, "Play status is \"stopped\".");
703 }
704 break;
705 case 3:
706 if (metadata_store.play_status != PS_PAUSED) {
707 metadata_store.play_status = PS_PAUSED;
708 debug(2, "Play status is \"paused\".");
709 }
710 break;
711 case 4:
712 if (metadata_store.play_status != PS_PLAYING) {
713 metadata_store.play_status = PS_PLAYING;
714 debug(2, "Play status changed to \"playing\".");
715 }
716 break;
717 default:
718 debug(1, "Unrecognised play status %d received.", r);
719 break;
3a4e29c5
MB
720 }
721 break;
a6acb606
MB
722 case 'cash': // shuffle status
723 t = sp - item_size;
724 r = *(unsigned char *)(t);
725 switch (r) {
726 case 0:
727 if (metadata_store.shuffle_status != SS_OFF) {
728 metadata_store.shuffle_status = SS_OFF;
729 debug(2, "Shuffle status is \"off\".");
730 }
731 break;
732 case 1:
733 if (metadata_store.shuffle_status != SS_ON) {
734 metadata_store.shuffle_status = SS_ON;
735 debug(2, "Shuffle status is \"on\".");
736 }
737 break;
738 default:
739 debug(1, "Unrecognised shuffle status %d received.", r);
740 break;
3a4e29c5
MB
741 }
742 break;
a6acb606
MB
743 case 'carp': // repeat status
744 t = sp - item_size;
745 r = *(unsigned char *)(t);
746 switch (r) {
747 case 0:
748 if (metadata_store.repeat_status != RS_OFF) {
749 metadata_store.repeat_status = RS_OFF;
750 debug(2, "Repeat status is \"none\".");
751 }
752 break;
753 case 1:
754 if (metadata_store.repeat_status != RS_ONE) {
755 metadata_store.repeat_status = RS_ONE;
756 debug(2, "Repeat status is \"one\".");
757 }
758 break;
759 case 2:
760 if (metadata_store.repeat_status != RS_ALL) {
761 metadata_store.repeat_status = RS_ALL;
762 debug(2, "Repeat status is \"all\".");
763 }
764 break;
765 default:
766 debug(1, "Unrecognised repeat status %d received.", r);
767 break;
768 }
3a4e29c5 769 break;
a6acb606
MB
770 case 'cann': // track name
771 debug(2, "DACP Track Name seen");
772 if (string_update_with_size(&metadata_store.track_name,
773 &metadata_store.track_name_changed, sp - item_size,
774 item_size)) {
775 debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
4aab0a6f 776 }
3a4e29c5 777 break;
a6acb606
MB
778 case 'cana': // artist name
779 debug(2, "DACP Artist Name seen");
780 if (string_update_with_size(&metadata_store.artist_name,
781 &metadata_store.artist_name_changed, sp - item_size,
782 item_size)) {
783 debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
3a4e29c5
MB
784 }
785 break;
a6acb606
MB
786 case 'canl': // album name
787 debug(2, "DACP Album Name seen");
788 if (string_update_with_size(&metadata_store.album_name,
789 &metadata_store.album_name_changed, sp - item_size,
790 item_size)) {
791 debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
792 }
85b17cda 793 break;
a6acb606
MB
794 case 'cang': // genre
795 debug(2, "DACP Genre seen");
796 if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
797 sp - item_size, item_size)) {
798 debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
3a4e29c5
MB
799 }
800 break;
a6acb606
MB
801 case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
802 // see reference above)
803 debug(2, "DACP Composite ID seen");
e8b8c6ed
MB
804 if ((metadata_store.item_composite_id_is_valid == 0) ||
805 (memcmp(metadata_store.item_composite_id, sp - item_size,
806 sizeof(metadata_store.item_composite_id)) != 0)) {
a6acb606
MB
807 memcpy(metadata_store.item_composite_id, sp - item_size,
808 sizeof(metadata_store.item_composite_id));
809 char st[33];
810 char *pt = st;
811 int it;
812 for (it = 0; it < 16; it++) {
813 snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
814 pt += 2;
815 }
816 *pt = 0;
817 debug(2, "Item composite ID changed to 0x%s.", st);
818 metadata_store.item_composite_id_changed = 1;
d67909b8 819 metadata_store.item_composite_id_is_valid = 1;
3a4e29c5
MB
820 }
821 break;
a6acb606
MB
822 case 'astm':
823 t = sp - item_size;
824 ui = ntohl(*(uint32_t *)(t));
825 debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
826 if (ui != metadata_store.songtime_in_milliseconds) {
827 metadata_store.songtime_in_milliseconds = ui;
828 metadata_store.songtime_in_milliseconds_changed = 1;
d67909b8 829 metadata_store.songtime_in_milliseconds_is_valid = 1;
a6acb606
MB
830 debug(2, "DACP Song Time set to: \"%u\"",
831 metadata_store.songtime_in_milliseconds);
86b6d7bf 832 }
3a4e29c5 833 break;
a6acb606
MB
834
835 /*
836 case 'mstt':
837 case 'cant':
838 case 'cast':
839 case 'cmmk':
840 case 'caas':
841 case 'caar':
842 t = sp - item_size;
843 r = ntohl(*(uint32_t *)(t));
844 printf(" %d", r);
845 printf(" (0x");
846 t = sp - item_size;
847 for (i = 0; i < item_size; i++) {
848 printf("%02x", *t & 0xff);
849 t++;
850 }
851 printf(")");
852 break;
853 case 'asai':
854 t = sp - item_size;
855 s = ntohl(*(uint32_t *)(t));
856 s = s << 32;
857 t += 4;
858 v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
859 s += v;
860 printf(" %lu", s);
861 printf(" (0x");
862 t = sp - item_size;
863 for (i = 0; i < item_size; i++) {
864 printf("%02x", *t & 0xff);
865 t++;
866 }
867 printf(")");
868 break;
869 */
4aab0a6f 870 default:
a6acb606
MB
871 /*
872 printf(" 0x");
873 t = sp - item_size;
874 for (i = 0; i < item_size; i++) {
875 printf("%02x", *t & 0xff);
876 t++;
877 }
878 */
4aab0a6f
MB
879 break;
880 }
a6acb606 881 // printf("\n");
3a4e29c5 882 }
85b17cda 883
a6acb606
MB
884 // finished possibly writing to the metadata hub
885 metadata_hub_modify_epilog(
886 1); // should really see if this can be made responsive to changes
887 } else {
888 debug(1, "Status Update not found.\n");
889 }
3a4e29c5 890 } else {
a6acb606 891 debug(1, "Can't find any content in playerstatusupdate request");
3a4e29c5 892 }
a6acb606
MB
893 } /* else {
894 if (result != 403)
895 debug(1, "Unexpected response %d to playerstatusupdate request", result);
896 } */
897 if (response) {
898 free(response);
899 response = NULL;
900 };
901 };
902 /*
903 strcpy(command,"nowplayingartwork?mw=320&mh=320");
904 debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
4aab0a6f
MB
905 if (response) {
906 free(response);
907 response = NULL;
a6acb606
MB
908 }
909 strcpy(command,"getproperty?properties=dmcp.volume");
910 debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
911 if (response) {
912 free(response);
913 response = NULL;
914 }
915 strcpy(command,"setproperty?dmcp.volume=100.000000");
916 debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
917 if (response) {
918 free(response);
919 response = NULL;
920 }
921 */
922 if (metadata_store.player_thread_active)
923 sleep(config.scan_interval_when_active);
924 else
925 sleep(config.scan_interval_when_inactive);
85b17cda 926 }
fe3b70b4 927 }
f1d45034 928 debug(1, "DACP monitor thread exiting -- should never happen.");
fe3b70b4
MB
929 pthread_exit(NULL);
930}
931
932void dacp_monitor_start() {
7fb89135 933 int rc;
a6acb606 934
b3cec5c6 935 rc = pthread_cond_init(&dacp_server_information_cv, NULL);
a6acb606
MB
936 if (rc)
937 debug(1, "Error initialising the DACP Server Information Condition Variable");
a6acb606 938
7fb89135
MB
939 pthread_mutexattr_t mta;
940
941 rc = pthread_mutexattr_init(&mta);
942 if (rc)
943 debug(1, "Error creating the DACP Conversation Lock Mutex Att Init");
944
945 rc = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
946 if (rc)
947 debug(1, "Error creating the DACP Conversation Lock Mutex Errorcheck");
948
949 // rc = pthread_mutexattr_setname_np(&mta, "DACP Conversation Lock");
950 // if (rc)
951 // debug(1,"Error creating the DACP Conversation Lock Mutex Set Name");
952
953 rc = pthread_mutex_init(&dacp_conversation_lock, &mta);
954 if (rc)
955 debug(1, "Error creating the DACP Conversation Lock Mutex Init");
f6d6700d
MB
956 // else
957 // debug(1, "DACP Conversation Lock Mutex Init");
7fb89135
MB
958
959 rc = pthread_mutexattr_destroy(&mta);
960 if (rc)
961 debug(1, "Error creating the DACP Conversation Lock Attr Destroy");
962
963 rc = pthread_mutexattr_init(&mta);
964 if (rc)
965 debug(1, "Error creating the DACP Server Information Lock Mutex Att Init");
966
967 rc = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
968 if (rc)
969 debug(1, "Error creating the DACP Server Information Lock Mutex Errorcheck");
970
971 // rc = pthread_mutexattr_setname_np(&mta, "DACP Conversation Lock");
972 // if (rc)
973 // debug(1,"Error creating the DACP Server Information Lock Mutex Set Name");
974
975 rc = pthread_mutex_init(&dacp_server_information_lock, &mta);
976 if (rc)
977 debug(1, "Error creating the DACP Server Information Lock Mutex Init");
978
979 rc = pthread_mutexattr_destroy(&mta);
980 if (rc)
981 debug(1, "Error creating the DACP Server Information Lock Attr Destroy");
982
0f68ff0c 983 memset(&dacp_server, 0, sizeof(dacp_server_record));
72079ada 984
fe3b70b4 985 pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL);
79e22063 986 dacp_monitor_initialised = 1;
fe3b70b4
MB
987}
988
f1d45034 989void dacp_monitor_stop() {
79e22063 990 if (dacp_monitor_initialised) { // only if it's been started and initialised
405a028f 991 debug(2, "dacp_monitor_stop");
79e22063
MB
992 pthread_cancel(dacp_monitor_thread);
993 pthread_join(dacp_monitor_thread, NULL);
994 pthread_mutex_destroy(&dacp_server_information_lock);
405a028f 995 debug(3, "DACP Conversation Lock Mutex Destroyed");
79e22063 996 pthread_mutex_destroy(&dacp_conversation_lock);
a6acb606 997 pthread_cond_destroy(&dacp_server_information_cv);
4e1f74db 998 debug(3, "DACP Server Information Condition Variable destroyed.");
6857d06b 999 if (dacp_server.active_remote_id) {
3fc95704
MB
1000 free(dacp_server.active_remote_id);
1001 dacp_server.active_remote_id = NULL;
6857d06b 1002 }
79e22063 1003 }
f1d45034
MB
1004}
1005
3198ec42
MB
1006uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
1007 char typecode[5];
1008 memcpy(typecode, *p, 4);
1009 typecode[4] = '\0';
1010 uint32_t type = ntohl(*(uint32_t *)*p);
1011 *p += 4;
419fe95a 1012 *length = ntohl(*(uint32_t *)*p);
3198ec42
MB
1013 *p += 4 + *length;
1014 // debug(1,"Type seen: '%s' of length %d",typecode,*length);
1015 return type;
1016}
1017
3a4e29c5 1018int dacp_get_client_volume(int32_t *result) {
07e46565 1019 // debug(1,"dacp_get_client_volume");
1637a79d 1020 char *server_reply = NULL;
3198ec42 1021 int32_t overall_volume = -1;
1637a79d 1022 ssize_t reply_size;
daab7a63 1023 // debug(1,"dacp_get_client_volume: dacp_send_command");
1637a79d
MB
1024 int response =
1025 dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
1026 if (response == 200) { // if we get an okay
3e2c3e83
MB
1027 char *sp = server_reply;
1028 int32_t item_size;
1029 if (reply_size >= 8) {
1030 if (dacp_tlv_crawl(&sp, &item_size) == 'cmgt') {
3e2c3e83
MB
1031 sp -= item_size; // drop down into the array -- don't skip over it
1032 reply_size -= 8;
1033 while (reply_size >= 8) {
1034 uint32_t type = dacp_tlv_crawl(&sp, &item_size);
1035 reply_size -= item_size + 8;
1036 if (type == 'cmvo') { // drop down into the dictionary -- don't skip over it
1037 char *t = sp - item_size;
419fe95a 1038 overall_volume = ntohl(*(uint32_t *)(t));
3e2c3e83
MB
1039 }
1040 }
1041 } else {
e0aa75a8 1042 debug(1, "Unexpected payload response from getproperty?properties=dmcp.volume");
3e2c3e83
MB
1043 }
1044 } else {
e0aa75a8 1045 debug(1, "Too short a response from getproperty?properties=dmcp.volume");
3e2c3e83 1046 }
fa6cafd0 1047 // debug(1, "Overall Volume is %d.", overall_volume);
668f7806 1048 }
ad6c721d 1049
668f7806
MB
1050 if (server_reply) {
1051 // debug(1, "Freeing response memory.");
1637a79d 1052 free(server_reply);
668f7806
MB
1053 server_reply = NULL;
1054 }
1055
73bf006c 1056 if (result) {
c36a7822 1057 *result = overall_volume;
07e46565 1058 // debug(1,"dacp_get_client_volume returns: %" PRId32 ".",overall_volume);
73bf006c 1059 }
3a4e29c5 1060 return response;
3198ec42
MB
1061}
1062
3e2c3e83 1063int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo) {
39f159bf 1064 debug(2, "dacp_set_include_speaker_volume to %" PRId32 ".", vo);
3198ec42
MB
1065 char message[1000];
1066 memset(message, 0, sizeof(message));
7fb89135
MB
1067 snprintf(message, sizeof(message),
1068 "setproperty?include-speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "", machine_number,
1069 vo);
27e42a66 1070 debug(2, "sending \"%s\"", message);
1637a79d
MB
1071 return send_simple_dacp_command(message);
1072 // should return 204
3198ec42
MB
1073}
1074
3e2c3e83 1075int dacp_set_speaker_volume(int64_t machine_number, int32_t vo) {
3198ec42
MB
1076 char message[1000];
1077 memset(message, 0, sizeof(message));
7fb89135
MB
1078 snprintf(message, sizeof(message), "setproperty?speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "",
1079 machine_number, vo);
27e42a66 1080 debug(2, "sending \"%s\"", message);
1637a79d
MB
1081 return send_simple_dacp_command(message);
1082 // should return 204
3198ec42
MB
1083}
1084
c36a7822
MB
1085int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
1086 int *actual_speaker_count) {
73bf006c 1087 // char typestring[5];
1637a79d 1088 char *server_reply = NULL;
3198ec42 1089 int speaker_index = -1; // will be incremented before use
c36a7822 1090 int speaker_count = -1; // will be fixed if there is no problem
1637a79d
MB
1091 ssize_t le;
1092
daab7a63 1093 // debug(1,"dacp_speaker_list: dacp_send_command");
1637a79d
MB
1094 int response = dacp_send_command("getspeakers", &server_reply, &le);
1095 if (response == 200) {
1096 char *sp = server_reply;
1097 int32_t item_size;
1098 if (le >= 8) {
1099 if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
1100 // debug(1,"Speakers:",item_size);
1101 sp -= item_size; // drop down into the array -- don't skip over it
1102 le -= 8;
1103 while (le >= 8) {
1104 uint32_t type = dacp_tlv_crawl(&sp, &item_size);
1105 if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
1106 // debug(1,">>>> Dictionary:");
1107 sp -= item_size;
1108 le -= 8;
1109 speaker_index++;
5f750ba9 1110 if (speaker_index == max_size_of_array) {
c36a7822 1111 return 413; // Payload Too Large -- too many speakers
5f750ba9 1112 }
1637a79d
MB
1113 speaker_info[speaker_index].active = 0;
1114 speaker_info[speaker_index].speaker_number = 0;
1115 speaker_info[speaker_index].volume = 0;
5f750ba9 1116 speaker_info[speaker_index].name[0] = '\0';
1637a79d
MB
1117 } else {
1118 le -= item_size + 8;
1119 char *t;
73bf006c 1120 // char u;
1637a79d
MB
1121 int32_t r;
1122 int64_t s, v;
1123 switch (type) {
1124 case 'minm':
1125 t = sp - item_size;
5f750ba9
MB
1126 strncpy((char *)&speaker_info[speaker_index].name, t,
1127 sizeof(speaker_info[speaker_index].name));
1128 speaker_info[speaker_index].name[sizeof(speaker_info[speaker_index].name) - 1] =
1129 '\0'; // just in case
1637a79d 1130 break;
1637a79d
MB
1131 case 'cmvo':
1132 t = sp - item_size;
419fe95a 1133 r = ntohl(*(uint32_t *)(t));
1637a79d 1134 speaker_info[speaker_index].volume = r;
c36a7822
MB
1135 // debug(1,"The individual volume of speaker \"%s\" is
1136 // \"%d\".",speaker_info[speaker_index].name,r);
1637a79d
MB
1137 break;
1138 case 'msma':
1139 t = sp - item_size;
1140 s = ntohl(*(uint32_t *)(t));
1141 s = s << 32;
1142 t += 4;
1143 v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
1144 s += v;
1145 speaker_info[speaker_index].speaker_number = s;
1146 // debug(1,"Speaker machine number: %ld",s);
1147 break;
1148
1149 case 'caia':
1150 speaker_info[speaker_index].active = 1;
1151 break;
1152 /*
1153 case 'caip':
1154 case 'cavd':
1155 case 'caiv':
3a4e29c5 1156 case 'cads':
c36a7822 1157
3a4e29c5
MB
1158 *(uint32_t *)typestring = htonl(type);
1159 typestring[4] = 0;
1160
c36a7822
MB
1161
1162
1637a79d
MB
1163 t = sp-item_size;
1164 u = *t;
3a4e29c5 1165 debug(1,"Type: '%s' Value: \"%d\".",typestring,u);
1637a79d
MB
1166 break;
1167 */
1168 default:
1169 break;
3198ec42
MB
1170 }
1171 }
3198ec42 1172 }
1637a79d 1173 // debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1);
3a4e29c5 1174 speaker_count = speaker_index + 1; // number of speaker entries in the array
3198ec42 1175 } else {
1637a79d 1176 debug(1, "Speaker array not found.");
3198ec42 1177 }
1637a79d
MB
1178 /*
1179 int i;
1180 for (i=0;i<le;i++) {
1181 if (*sp < ' ')
1182 debug(1,"%d %02x", i, *sp);
1183 else
1184 debug(1,"%d %02x '%c'", i, *sp,*sp);
1185 sp++;
1186 }
1187 */
3198ec42 1188 } else {
1637a79d 1189 debug(1, "Can't find any content in dacp speakers request");
3198ec42 1190 }
1637a79d
MB
1191 free(server_reply);
1192 server_reply = NULL;
3198ec42 1193 } else {
668f7806
MB
1194 // debug(1, "Unexpected response %d to dacp speakers request", response);
1195 if (server_reply) {
1196 debug(1, "Freeing response memory.");
1197 free(server_reply);
1198 server_reply = NULL;
1199 }
3198ec42 1200 }
3a4e29c5
MB
1201 if (actual_speaker_count)
1202 *actual_speaker_count = speaker_count;
1203 return response;
3198ec42 1204}
3e2c3e83 1205
3a4e29c5 1206int dacp_get_volume(int32_t *the_actual_volume) {
3e2c3e83
MB
1207 // get the speaker volume information from the DACP source and store it in the metadata_hub
1208 // A volume command has been sent from the client
1209 // let's get the master volume from the DACP remote control
1210 struct dacp_speaker_stuff speaker_info[50];
1211 // we need the overall volume and the speakers information to get this device's relative volume to
1212 // calculate the real volume
1213
3a4e29c5
MB
1214 int32_t overall_volume = 0;
1215 int32_t actual_volume = 0;
1216 int http_response = dacp_get_client_volume(&overall_volume);
c36a7822 1217 if (http_response == 200) {
07e46565 1218 // debug(1,"Overall volume is: %u.",overall_volume);
3a4e29c5 1219 int speaker_count = 0;
c36a7822
MB
1220 http_response = dacp_get_speaker_list((dacp_spkr_stuff *)&speaker_info, 50, &speaker_count);
1221 if (http_response == 200) {
3a4e29c5 1222 // get our machine number
03d291f4
MB
1223 uint16_t *hn = (uint16_t *)config.ap1_prefix;
1224 uint32_t *ln = (uint32_t *)(config.ap1_prefix + 2);
3a4e29c5
MB
1225 uint64_t t1 = ntohs(*hn);
1226 uint64_t t2 = ntohl(*ln);
1227 int64_t machine_number = (t1 << 32) + t2; // this form is useful
1228
1229 // Let's find our own speaker in the array and pick up its relative volume
1230 int i;
1231 int32_t relative_volume = 0;
1232 for (i = 0; i < speaker_count; i++) {
1233 if (speaker_info[i].speaker_number == machine_number) {
3a4e29c5 1234 relative_volume = speaker_info[i].volume;
07e46565 1235 /*
73bf006c 1236 debug(1,"Our speaker was found with a relative volume of: %u.",relative_volume);
c36a7822 1237
3a4e29c5
MB
1238 if (speaker_info[i].active)
1239 debug(1,"Our speaker is active.");
1240 else
1241 debug(1,"Our speaker is inactive.");
07e46565 1242 */
3a4e29c5
MB
1243 }
1244 }
1245 actual_volume = (overall_volume * relative_volume + 50) / 100;
c36a7822
MB
1246 // debug(1,"Overall volume: %d, relative volume: %d%, actual volume:
1247 // %d.",overall_volume,relative_volume,actual_volume);
3a4e29c5 1248 // debug(1,"Our actual speaker volume is %d.",actual_volume);
c36a7822
MB
1249 // metadata_hub_modify_prolog();
1250 // metadata_store.speaker_volume = actual_volume;
1251 // metadata_hub_modify_epilog(1);
3a4e29c5 1252 } else {
d3365f8b 1253 debug(2, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
3e2c3e83 1254 }
77a5cf1e 1255 } else if ((http_response != 400) && (http_response != 490)) {
c02e53ea 1256 debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
c36a7822 1257 }
73bf006c 1258 if (the_actual_volume) {
07e46565 1259 // debug(1,"dacp_get_volume returns %d.",actual_volume);
3a4e29c5 1260 *the_actual_volume = actual_volume;
73bf006c
MB
1261 }
1262 return http_response;
3e2c3e83 1263}
39f159bf
MB
1264
1265int dacp_set_volume(int32_t vo) {
1266 int http_response = 492; // argument out of range
1267 if ((vo >= 0) && (vo <= 100)) {
1268 // get the information we need -- the absolute volume, the speaker list, our ID
1269 struct dacp_speaker_stuff speaker_info[50];
1270 int32_t overall_volume;
1271 http_response = dacp_get_client_volume(&overall_volume);
1272 if (http_response == 200) {
1273 int speaker_count;
1274 http_response = dacp_get_speaker_list((dacp_spkr_stuff *)&speaker_info, 50, &speaker_count);
1275 if (http_response == 200) {
1276 // get our machine number
03d291f4
MB
1277 uint16_t *hn = (uint16_t *)config.ap1_prefix;
1278 uint32_t *ln = (uint32_t *)(config.ap1_prefix + 2);
39f159bf
MB
1279 uint64_t t1 = ntohs(*hn);
1280 uint64_t t2 = ntohl(*ln);
1281 int64_t machine_number = (t1 << 32) + t2; // this form is useful
1282
1283 // Let's find our own speaker in the array and pick up its relative volume
1284 int i;
1285 int32_t active_speakers = 0;
1286 for (i = 0; i < speaker_count; i++) {
1287 if (speaker_info[i].speaker_number == machine_number) {
1288 debug(2, "Our speaker number found: %ld with relative volume.", machine_number,
1289 speaker_info[i].volume);
1290 }
1291 if (speaker_info[i].active == 1) {
1292 active_speakers++;
1293 }
1294 }
1295
1296 if (active_speakers == 1) {
1297 // must be just this speaker
1298 debug(2, "Remote-setting volume to %d on just one speaker.", vo);
1299 http_response = dacp_set_include_speaker_volume(machine_number, vo);
1300 } else if (active_speakers == 0) {
1301 debug(2, "No speakers!");
1302 } else {
1303 debug(2, "Speakers: %d, active: %d", speaker_count, active_speakers);
1304 if (vo >= overall_volume) {
1305 debug(2, "Multiple speakers active, but desired new volume is highest");
1306 http_response = dacp_set_include_speaker_volume(machine_number, vo);
1307 } else {
1308 // the desired volume is less than the current overall volume and there is more than
1309 // one
1310 // speaker
1311 // we must find out the highest other speaker volume.
1312 // If the desired volume is less than it, we must set the current_overall volume to
1313 // that
1314 // highest volume
1315 // and set our volume relative to it.
1316 // If the desired volume is greater than the highest current volume, then we can just
1317 // go
1318 // ahead
1319 // with dacp_set_include_speaker_volume, setting the new current overall volume to the
1320 // desired new level
1321 // with the speaker at 100%
1322
1323 int32_t highest_other_volume = 0;
1324 for (i = 0; i < speaker_count; i++) {
1325 if ((speaker_info[i].speaker_number != machine_number) &&
1326 (speaker_info[i].active == 1) &&
1327 (speaker_info[i].volume > highest_other_volume)) {
1328 highest_other_volume = speaker_info[i].volume;
1329 }
1330 }
1331 highest_other_volume = (highest_other_volume * overall_volume + 50) / 100;
1332 if (highest_other_volume <= vo) {
1333 debug(2,
1334 "Highest other volume %d is less than or equal to the desired new volume %d.",
1335 highest_other_volume, vo);
1336 http_response = dacp_set_include_speaker_volume(machine_number, vo);
1337 } else {
1338 debug(2, "Highest other volume %d is greater than the desired new volume %d.",
1339 highest_other_volume, vo);
1340 // if the present overall volume is higher than the highest other volume at present,
1341 // then bring it down to it.
1342 if (overall_volume > highest_other_volume) {
1343 debug(2, "Lower overall volume to new highest volume.");
1344 http_response = dacp_set_include_speaker_volume(
1345 machine_number,
1346 highest_other_volume); // set the overall volume to the highest one
1347 }
14bfba27
MB
1348 if (highest_other_volume != 0) {
1349 int32_t desired_relative_volume =
1350 (vo * 100 + (highest_other_volume / 2)) / highest_other_volume;
1351 debug(2, "Set our speaker volume relative to the highest volume.");
1352 http_response = dacp_set_speaker_volume(
1353 machine_number,
1354 desired_relative_volume); // set the overall volume to the highest one
1355 }
39f159bf
MB
1356 }
1357 }
1358 }
1359 } else {
1360 debug(2, "Can't get speakers list");
1361 }
1362 } else {
1363 debug(2, "Can't get client volume");
1364 }
1365
1366 } else {
1367 debug(2, "Invalid volume: %d -- ignored.", vo);
1368 }
1369 return http_response;
1370}