|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
/*
|
|
|
|
|
* DACP protocol handler. This file is part of Shairport Sync.
|
|
|
|
|
* Copyright (c) Mike Brady 2017 -- 2019
|
|
|
|
|
* Copyright (c) Mike Brady 2017 -- 2020
|
|
|
|
|
* All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person
|
|
|
|
@@ -49,7 +49,7 @@
|
|
|
|
|
typedef struct {
|
|
|
|
|
int players_connection_thread_index; // the connection thread index when a player thread is
|
|
|
|
|
// associated with this, zero otherwise
|
|
|
|
|
int scan_enable; // set to 1 if if sacanning should be considered
|
|
|
|
|
int scan_enable; // set to 1 if scanning should be considered
|
|
|
|
|
char dacp_id[256]; // the DACP ID string
|
|
|
|
|
uint16_t port; // zero if no port discovered
|
|
|
|
|
short connection_family; // AF_INET6 or AF_INET
|
|
|
|
@@ -123,7 +123,7 @@ static const struct http_funcs responseFuncs = {
|
|
|
|
|
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
static pthread_mutex_t dacp_conversation_lock;
|
|
|
|
|
static pthread_mutex_t dacp_server_information_lock;
|
|
|
|
|
static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
|
|
|
|
|
static pthread_cond_t dacp_server_information_cv;
|
|
|
|
|
|
|
|
|
|
void addrinfo_cleanup(void *arg) {
|
|
|
|
|
// debug(1, "addrinfo cleanup called.");
|
|
|
|
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
|
|
|
|
// debug(1,"dacp_send_command: command is: \"%s\".",command);
|
|
|
|
|
|
|
|
|
|
if (dacp_server.port == 0) {
|
|
|
|
|
debug(1, "No DACP port specified yet");
|
|
|
|
|
debug(2, "No DACP port specified yet");
|
|
|
|
|
result = 490; // no port specified
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
@@ -169,6 +169,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
|
|
|
|
// 493 Client failed to send a message
|
|
|
|
|
// 492 Argument out of range
|
|
|
|
|
// 491 Client refused connection
|
|
|
|
|
// 490 No port specified
|
|
|
|
|
|
|
|
|
|
struct addrinfo hints, *res;
|
|
|
|
|
int sockfd;
|
|
|
|
@@ -421,9 +422,6 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
|
|
|
|
|
dacp_server.always_use_revision_number_1 = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
|
|
|
|
|
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
|
|
|
|
|
metadata_store.dacp_server_active = dacp_server.scan_enable;
|
|
|
|
@@ -513,14 +511,40 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
|
|
|
|
"flag value of %d",
|
|
|
|
|
ch);
|
|
|
|
|
metadata_hub_modify_epilog(ch);
|
|
|
|
|
while (dacp_server.scan_enable == 0) {
|
|
|
|
|
// debug(1, "dacp_monitor_thread_code wait for an event to possible enable scan");
|
|
|
|
|
pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
|
|
|
|
|
// debug(1,"dacp_monitor_thread_code wake up.");
|
|
|
|
|
|
|
|
|
|
uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
|
|
|
|
|
|
|
|
|
|
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
|
|
|
|
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
|
|
|
|
|
uint64_t sec = time_of_wakeup_ns / 1000000000;
|
|
|
|
|
uint64_t nsec = time_of_wakeup_ns % 1000000000;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef COMPILE_FOR_OSX
|
|
|
|
|
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
|
|
|
|
|
uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
struct timespec time_to_wait;
|
|
|
|
|
time_to_wait.tv_sec = sec;
|
|
|
|
|
time_to_wait.tv_nsec = nsec;
|
|
|
|
|
|
|
|
|
|
while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
|
|
|
|
|
// debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
|
|
|
|
|
|
|
|
|
|
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
|
|
|
|
result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
|
|
|
|
|
&time_to_wait); // this is a pthread cancellation point
|
|
|
|
|
// debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %" PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef COMPILE_FOR_OSX
|
|
|
|
|
result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv, &dacp_server_information_lock, &time_to_wait);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
if (dacp_server.scan_enable == 1) {
|
|
|
|
|
bad_result_count = 0;
|
|
|
|
|
idle_scan_count = 0;
|
|
|
|
|
}
|
|
|
|
|
// so dacp_server.scan_enable will be true at this point
|
|
|
|
|
bad_result_count = 0;
|
|
|
|
|
idle_scan_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
|
|
|
|
@@ -528,360 +552,367 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
|
|
|
|
result = dacp_get_volume(&the_volume); // just want the http code
|
|
|
|
|
pthread_cleanup_pop(1);
|
|
|
|
|
|
|
|
|
|
scan_index++;
|
|
|
|
|
// debug(1,"DACP Scan Result: %d.", result);
|
|
|
|
|
|
|
|
|
|
if ((result == 200) || (result == 400)) {
|
|
|
|
|
bad_result_count = 0;
|
|
|
|
|
if (result == 490) { // 490 means no port was specified
|
|
|
|
|
if ((dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
|
|
|
|
|
// debug(1,"mdns_dacp_monitor_set_id");
|
|
|
|
|
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
|
|
|
|
|
bad_result_count++;
|
|
|
|
|
}
|
|
|
|
|
scan_index++;
|
|
|
|
|
// debug(1,"DACP Scan Result: %d.", result);
|
|
|
|
|
|
|
|
|
|
// here, do the debouncing calculations to see
|
|
|
|
|
// if the dacp server and the
|
|
|
|
|
// advanced dacp server are available
|
|
|
|
|
if ((result == 200) || (result == 400)) {
|
|
|
|
|
bad_result_count = 0;
|
|
|
|
|
} else {
|
|
|
|
|
if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
|
|
|
|
|
bad_result_count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -1 means we don't know because some bad statuses have been reported
|
|
|
|
|
// 0 means definitely no
|
|
|
|
|
// +1 means definitely yes
|
|
|
|
|
// here, do the debouncing calculations to see
|
|
|
|
|
// if the dacp server and the
|
|
|
|
|
// advanced dacp server are available
|
|
|
|
|
|
|
|
|
|
int dacp_server_status_now = -1;
|
|
|
|
|
int advanced_dacp_server_status_now = -1;
|
|
|
|
|
// -1 means we don't know because some bad statuses have been reported
|
|
|
|
|
// 0 means definitely no
|
|
|
|
|
// +1 means definitely yes
|
|
|
|
|
|
|
|
|
|
if (bad_result_count == 0) {
|
|
|
|
|
dacp_server_status_now = 1;
|
|
|
|
|
if (result == 200)
|
|
|
|
|
advanced_dacp_server_status_now = 1;
|
|
|
|
|
else if (result == 400)
|
|
|
|
|
int dacp_server_status_now = -1;
|
|
|
|
|
int advanced_dacp_server_status_now = -1;
|
|
|
|
|
|
|
|
|
|
if (bad_result_count == 0) {
|
|
|
|
|
dacp_server_status_now = 1;
|
|
|
|
|
if (result == 200)
|
|
|
|
|
advanced_dacp_server_status_now = 1;
|
|
|
|
|
else if (result == 400)
|
|
|
|
|
advanced_dacp_server_status_now = 0;
|
|
|
|
|
} else if (bad_result_count ==
|
|
|
|
|
config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
|
|
|
|
|
// then it's gone
|
|
|
|
|
dacp_server_status_now = 0;
|
|
|
|
|
advanced_dacp_server_status_now = 0;
|
|
|
|
|
} else if (bad_result_count ==
|
|
|
|
|
config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
|
|
|
|
|
// then it's gone
|
|
|
|
|
dacp_server_status_now = 0;
|
|
|
|
|
advanced_dacp_server_status_now = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (metadata_store.player_thread_active == 0)
|
|
|
|
|
idle_scan_count++;
|
|
|
|
|
else
|
|
|
|
|
idle_scan_count = 0;
|
|
|
|
|
|
|
|
|
|
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
|
|
|
|
|
idle_scan_count);
|
|
|
|
|
|
|
|
|
|
/* not used
|
|
|
|
|
// decide if idle for too long
|
|
|
|
|
if (idle_scan_count == config.scan_max_inactive_count) {
|
|
|
|
|
debug(2, "DACP server status scanning stopped.");
|
|
|
|
|
dacp_server.scan_enable = 0;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int update_needed = 0;
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
|
|
|
|
|
if (metadata_store.dacp_server_active != dacp_server_status_now) {
|
|
|
|
|
debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
|
|
|
|
|
metadata_store.dacp_server_active = dacp_server_status_now;
|
|
|
|
|
update_needed = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (advanced_dacp_server_status_now !=
|
|
|
|
|
-1) { // if advanced_dacp_server_status_now is actually known...
|
|
|
|
|
if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
|
|
|
|
|
debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
|
|
|
|
|
metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
|
|
|
|
|
update_needed = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metadata_hub_modify_epilog(update_needed);
|
|
|
|
|
if (metadata_store.player_thread_active == 0)
|
|
|
|
|
idle_scan_count++;
|
|
|
|
|
else
|
|
|
|
|
idle_scan_count = 0;
|
|
|
|
|
|
|
|
|
|
// pthread_mutex_unlock(&dacp_server_information_lock);
|
|
|
|
|
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
|
|
|
|
|
// dacp_server.ip_string, dacp_server.port, scan_index);
|
|
|
|
|
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
|
|
|
|
|
idle_scan_count);
|
|
|
|
|
|
|
|
|
|
if (result == 200) {
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
int diff = metadata_store.speaker_volume != the_volume;
|
|
|
|
|
if (diff)
|
|
|
|
|
metadata_store.speaker_volume = the_volume;
|
|
|
|
|
metadata_hub_modify_epilog(diff);
|
|
|
|
|
|
|
|
|
|
ssize_t le;
|
|
|
|
|
char *response = NULL;
|
|
|
|
|
int32_t item_size;
|
|
|
|
|
char command[1024] = "";
|
|
|
|
|
if (always_use_revision_number_1 != 0) // for forked-daapd
|
|
|
|
|
revision_number = 1;
|
|
|
|
|
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
|
|
|
|
|
revision_number);
|
|
|
|
|
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
|
|
|
|
|
result = dacp_send_command(command, &response, &le);
|
|
|
|
|
// debug(1,"Response to \"%s\" is %d.",command,result);
|
|
|
|
|
// remember: unless the revision_number you pass in is 1,
|
|
|
|
|
// response will be 200 only if there's something new to report.
|
|
|
|
|
if (result == 200) {
|
|
|
|
|
// if (0) {
|
|
|
|
|
char *sp = response;
|
|
|
|
|
if (le >= 8) {
|
|
|
|
|
// here start looking for the contents of the status update
|
|
|
|
|
if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
|
|
|
|
|
// here, we know that we are receiving playerstatusupdates, so set a flag
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
// debug(1, "playstatusupdate release track metadata");
|
|
|
|
|
// metadata_hub_reset_track_metadata();
|
|
|
|
|
// metadata_store.playerstatusupdates_are_received = 1;
|
|
|
|
|
sp -= item_size; // drop down into the array -- don't skip over it
|
|
|
|
|
le -= 8;
|
|
|
|
|
// char typestring[5];
|
|
|
|
|
// we need to acquire the metadata data structure and possibly update it
|
|
|
|
|
while (le >= 8) {
|
|
|
|
|
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
|
|
|
|
|
le -= item_size + 8;
|
|
|
|
|
char *t;
|
|
|
|
|
// char u;
|
|
|
|
|
// char *st;
|
|
|
|
|
int32_t r;
|
|
|
|
|
uint32_t ui;
|
|
|
|
|
// uint64_t v;
|
|
|
|
|
// int i;
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'cmsr': // revision number
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
revision_number = ntohl(*(uint32_t *)(t));
|
|
|
|
|
// debug(1,"New revision number received: %d", revision_number);
|
|
|
|
|
break;
|
|
|
|
|
case 'caps': // play status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 2:
|
|
|
|
|
if (metadata_store.play_status != PS_STOPPED) {
|
|
|
|
|
metadata_store.play_status = PS_STOPPED;
|
|
|
|
|
debug(2, "Play status is \"stopped\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
if (metadata_store.play_status != PS_PAUSED) {
|
|
|
|
|
metadata_store.play_status = PS_PAUSED;
|
|
|
|
|
debug(2, "Play status is \"paused\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
if (metadata_store.play_status != PS_PLAYING) {
|
|
|
|
|
metadata_store.play_status = PS_PLAYING;
|
|
|
|
|
debug(2, "Play status changed to \"playing\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised play status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cash': // shuffle status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (metadata_store.shuffle_status != SS_OFF) {
|
|
|
|
|
metadata_store.shuffle_status = SS_OFF;
|
|
|
|
|
debug(2, "Shuffle status is \"off\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (metadata_store.shuffle_status != SS_ON) {
|
|
|
|
|
metadata_store.shuffle_status = SS_ON;
|
|
|
|
|
debug(2, "Shuffle status is \"on\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised shuffle status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'carp': // repeat status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (metadata_store.repeat_status != RS_OFF) {
|
|
|
|
|
metadata_store.repeat_status = RS_OFF;
|
|
|
|
|
debug(2, "Repeat status is \"none\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (metadata_store.repeat_status != RS_ONE) {
|
|
|
|
|
metadata_store.repeat_status = RS_ONE;
|
|
|
|
|
debug(2, "Repeat status is \"one\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
if (metadata_store.repeat_status != RS_ALL) {
|
|
|
|
|
metadata_store.repeat_status = RS_ALL;
|
|
|
|
|
debug(2, "Repeat status is \"all\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised repeat status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cann': // track name
|
|
|
|
|
debug(2, "DACP Track Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.track_name,
|
|
|
|
|
&metadata_store.track_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cana': // artist name
|
|
|
|
|
debug(2, "DACP Artist Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.artist_name,
|
|
|
|
|
&metadata_store.artist_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'canl': // album name
|
|
|
|
|
debug(2, "DACP Album Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.album_name,
|
|
|
|
|
&metadata_store.album_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cang': // genre
|
|
|
|
|
debug(2, "DACP Genre seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
|
|
|
|
|
sp - item_size, item_size)) {
|
|
|
|
|
debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
|
|
|
|
|
// see reference above)
|
|
|
|
|
debug(2, "DACP Composite ID seen");
|
|
|
|
|
if (memcmp(metadata_store.item_composite_id, sp - item_size,
|
|
|
|
|
sizeof(metadata_store.item_composite_id)) != 0) {
|
|
|
|
|
memcpy(metadata_store.item_composite_id, sp - item_size,
|
|
|
|
|
sizeof(metadata_store.item_composite_id));
|
|
|
|
|
char st[33];
|
|
|
|
|
char *pt = st;
|
|
|
|
|
int it;
|
|
|
|
|
for (it = 0; it < 16; it++) {
|
|
|
|
|
snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
|
|
|
|
|
pt += 2;
|
|
|
|
|
}
|
|
|
|
|
*pt = 0;
|
|
|
|
|
debug(2, "Item composite ID changed to 0x%s.", st);
|
|
|
|
|
metadata_store.item_composite_id_changed = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'astm':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
ui = ntohl(*(uint32_t *)(t));
|
|
|
|
|
debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
|
|
|
|
|
if (ui != metadata_store.songtime_in_milliseconds) {
|
|
|
|
|
metadata_store.songtime_in_milliseconds = ui;
|
|
|
|
|
metadata_store.songtime_in_milliseconds_changed = 1;
|
|
|
|
|
debug(2, "DACP Song Time set to: \"%u\"",
|
|
|
|
|
metadata_store.songtime_in_milliseconds);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
case 'mstt':
|
|
|
|
|
case 'cant':
|
|
|
|
|
case 'cast':
|
|
|
|
|
case 'cmmk':
|
|
|
|
|
case 'caas':
|
|
|
|
|
case 'caar':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = ntohl(*(uint32_t *)(t));
|
|
|
|
|
printf(" %d", r);
|
|
|
|
|
printf(" (0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
printf(")");
|
|
|
|
|
break;
|
|
|
|
|
case 'asai':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
s = ntohl(*(uint32_t *)(t));
|
|
|
|
|
s = s << 32;
|
|
|
|
|
t += 4;
|
|
|
|
|
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
|
|
|
|
|
s += v;
|
|
|
|
|
printf(" %lu", s);
|
|
|
|
|
printf(" (0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
printf(")");
|
|
|
|
|
break;
|
|
|
|
|
*/
|
|
|
|
|
default:
|
|
|
|
|
/*
|
|
|
|
|
printf(" 0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// finished possibly writing to the metadata hub
|
|
|
|
|
metadata_hub_modify_epilog(
|
|
|
|
|
1); // should really see if this can be made responsive to changes
|
|
|
|
|
} else {
|
|
|
|
|
debug(1, "Status Update not found.\n");
|
|
|
|
|
/* not used
|
|
|
|
|
// decide if idle for too long
|
|
|
|
|
if (idle_scan_count == config.scan_max_inactive_count) {
|
|
|
|
|
debug(2, "DACP server status scanning stopped.");
|
|
|
|
|
dacp_server.scan_enable = 0;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debug(1, "Can't find any content in playerstatusupdate request");
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int update_needed = 0;
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
|
|
|
|
|
if (metadata_store.dacp_server_active != dacp_server_status_now) {
|
|
|
|
|
debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
|
|
|
|
|
metadata_store.dacp_server_active = dacp_server_status_now;
|
|
|
|
|
update_needed = 1;
|
|
|
|
|
}
|
|
|
|
|
} /* else {
|
|
|
|
|
if (result != 403)
|
|
|
|
|
debug(1, "Unexpected response %d to playerstatusupdate request", result);
|
|
|
|
|
} */
|
|
|
|
|
}
|
|
|
|
|
if (advanced_dacp_server_status_now !=
|
|
|
|
|
-1) { // if advanced_dacp_server_status_now is actually known...
|
|
|
|
|
if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
|
|
|
|
|
debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
|
|
|
|
|
metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
|
|
|
|
|
update_needed = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metadata_hub_modify_epilog(update_needed);
|
|
|
|
|
|
|
|
|
|
// pthread_mutex_unlock(&dacp_server_information_lock);
|
|
|
|
|
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
|
|
|
|
|
// dacp_server.ip_string, dacp_server.port, scan_index);
|
|
|
|
|
|
|
|
|
|
if (result == 200) {
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
int diff = metadata_store.speaker_volume != the_volume;
|
|
|
|
|
if (diff)
|
|
|
|
|
metadata_store.speaker_volume = the_volume;
|
|
|
|
|
metadata_hub_modify_epilog(diff);
|
|
|
|
|
|
|
|
|
|
ssize_t le;
|
|
|
|
|
char *response = NULL;
|
|
|
|
|
int32_t item_size;
|
|
|
|
|
char command[1024] = "";
|
|
|
|
|
if (always_use_revision_number_1 != 0) // for forked-daapd
|
|
|
|
|
revision_number = 1;
|
|
|
|
|
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
|
|
|
|
|
revision_number);
|
|
|
|
|
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
|
|
|
|
|
result = dacp_send_command(command, &response, &le);
|
|
|
|
|
// debug(1,"Response to \"%s\" is %d.",command,result);
|
|
|
|
|
// remember: unless the revision_number you pass in is 1,
|
|
|
|
|
// response will be 200 only if there's something new to report.
|
|
|
|
|
if (result == 200) {
|
|
|
|
|
// if (0) {
|
|
|
|
|
char *sp = response;
|
|
|
|
|
if (le >= 8) {
|
|
|
|
|
// here start looking for the contents of the status update
|
|
|
|
|
if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
|
|
|
|
|
// here, we know that we are receiving playerstatusupdates, so set a flag
|
|
|
|
|
metadata_hub_modify_prolog();
|
|
|
|
|
// debug(1, "playstatusupdate release track metadata");
|
|
|
|
|
// metadata_hub_reset_track_metadata();
|
|
|
|
|
// metadata_store.playerstatusupdates_are_received = 1;
|
|
|
|
|
sp -= item_size; // drop down into the array -- don't skip over it
|
|
|
|
|
le -= 8;
|
|
|
|
|
// char typestring[5];
|
|
|
|
|
// we need to acquire the metadata data structure and possibly update it
|
|
|
|
|
while (le >= 8) {
|
|
|
|
|
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
|
|
|
|
|
le -= item_size + 8;
|
|
|
|
|
char *t;
|
|
|
|
|
// char u;
|
|
|
|
|
// char *st;
|
|
|
|
|
int32_t r;
|
|
|
|
|
uint32_t ui;
|
|
|
|
|
// uint64_t v;
|
|
|
|
|
// int i;
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'cmsr': // revision number
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
revision_number = ntohl(*(uint32_t *)(t));
|
|
|
|
|
// debug(1,"New revision number received: %d", revision_number);
|
|
|
|
|
break;
|
|
|
|
|
case 'caps': // play status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 2:
|
|
|
|
|
if (metadata_store.play_status != PS_STOPPED) {
|
|
|
|
|
metadata_store.play_status = PS_STOPPED;
|
|
|
|
|
debug(2, "Play status is \"stopped\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
if (metadata_store.play_status != PS_PAUSED) {
|
|
|
|
|
metadata_store.play_status = PS_PAUSED;
|
|
|
|
|
debug(2, "Play status is \"paused\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
if (metadata_store.play_status != PS_PLAYING) {
|
|
|
|
|
metadata_store.play_status = PS_PLAYING;
|
|
|
|
|
debug(2, "Play status changed to \"playing\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised play status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cash': // shuffle status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (metadata_store.shuffle_status != SS_OFF) {
|
|
|
|
|
metadata_store.shuffle_status = SS_OFF;
|
|
|
|
|
debug(2, "Shuffle status is \"off\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (metadata_store.shuffle_status != SS_ON) {
|
|
|
|
|
metadata_store.shuffle_status = SS_ON;
|
|
|
|
|
debug(2, "Shuffle status is \"on\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised shuffle status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'carp': // repeat status
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = *(unsigned char *)(t);
|
|
|
|
|
switch (r) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (metadata_store.repeat_status != RS_OFF) {
|
|
|
|
|
metadata_store.repeat_status = RS_OFF;
|
|
|
|
|
debug(2, "Repeat status is \"none\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (metadata_store.repeat_status != RS_ONE) {
|
|
|
|
|
metadata_store.repeat_status = RS_ONE;
|
|
|
|
|
debug(2, "Repeat status is \"one\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
if (metadata_store.repeat_status != RS_ALL) {
|
|
|
|
|
metadata_store.repeat_status = RS_ALL;
|
|
|
|
|
debug(2, "Repeat status is \"all\".");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug(1, "Unrecognised repeat status %d received.", r);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cann': // track name
|
|
|
|
|
debug(2, "DACP Track Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.track_name,
|
|
|
|
|
&metadata_store.track_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cana': // artist name
|
|
|
|
|
debug(2, "DACP Artist Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.artist_name,
|
|
|
|
|
&metadata_store.artist_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'canl': // album name
|
|
|
|
|
debug(2, "DACP Album Name seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.album_name,
|
|
|
|
|
&metadata_store.album_name_changed, sp - item_size,
|
|
|
|
|
item_size)) {
|
|
|
|
|
debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'cang': // genre
|
|
|
|
|
debug(2, "DACP Genre seen");
|
|
|
|
|
if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
|
|
|
|
|
sp - item_size, item_size)) {
|
|
|
|
|
debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
|
|
|
|
|
// see reference above)
|
|
|
|
|
debug(2, "DACP Composite ID seen");
|
|
|
|
|
if (memcmp(metadata_store.item_composite_id, sp - item_size,
|
|
|
|
|
sizeof(metadata_store.item_composite_id)) != 0) {
|
|
|
|
|
memcpy(metadata_store.item_composite_id, sp - item_size,
|
|
|
|
|
sizeof(metadata_store.item_composite_id));
|
|
|
|
|
char st[33];
|
|
|
|
|
char *pt = st;
|
|
|
|
|
int it;
|
|
|
|
|
for (it = 0; it < 16; it++) {
|
|
|
|
|
snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
|
|
|
|
|
pt += 2;
|
|
|
|
|
}
|
|
|
|
|
*pt = 0;
|
|
|
|
|
debug(2, "Item composite ID changed to 0x%s.", st);
|
|
|
|
|
metadata_store.item_composite_id_changed = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'astm':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
ui = ntohl(*(uint32_t *)(t));
|
|
|
|
|
debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
|
|
|
|
|
if (ui != metadata_store.songtime_in_milliseconds) {
|
|
|
|
|
metadata_store.songtime_in_milliseconds = ui;
|
|
|
|
|
metadata_store.songtime_in_milliseconds_changed = 1;
|
|
|
|
|
debug(2, "DACP Song Time set to: \"%u\"",
|
|
|
|
|
metadata_store.songtime_in_milliseconds);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
case 'mstt':
|
|
|
|
|
case 'cant':
|
|
|
|
|
case 'cast':
|
|
|
|
|
case 'cmmk':
|
|
|
|
|
case 'caas':
|
|
|
|
|
case 'caar':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
r = ntohl(*(uint32_t *)(t));
|
|
|
|
|
printf(" %d", r);
|
|
|
|
|
printf(" (0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
printf(")");
|
|
|
|
|
break;
|
|
|
|
|
case 'asai':
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
s = ntohl(*(uint32_t *)(t));
|
|
|
|
|
s = s << 32;
|
|
|
|
|
t += 4;
|
|
|
|
|
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
|
|
|
|
|
s += v;
|
|
|
|
|
printf(" %lu", s);
|
|
|
|
|
printf(" (0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
printf(")");
|
|
|
|
|
break;
|
|
|
|
|
*/
|
|
|
|
|
default:
|
|
|
|
|
/*
|
|
|
|
|
printf(" 0x");
|
|
|
|
|
t = sp - item_size;
|
|
|
|
|
for (i = 0; i < item_size; i++) {
|
|
|
|
|
printf("%02x", *t & 0xff);
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// finished possibly writing to the metadata hub
|
|
|
|
|
metadata_hub_modify_epilog(
|
|
|
|
|
1); // should really see if this can be made responsive to changes
|
|
|
|
|
} else {
|
|
|
|
|
debug(1, "Status Update not found.\n");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debug(1, "Can't find any content in playerstatusupdate request");
|
|
|
|
|
}
|
|
|
|
|
} /* else {
|
|
|
|
|
if (result != 403)
|
|
|
|
|
debug(1, "Unexpected response %d to playerstatusupdate request", result);
|
|
|
|
|
} */
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
strcpy(command,"nowplayingartwork?mw=320&mh=320");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
strcpy(command,"nowplayingartwork?mw=320&mh=320");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
}
|
|
|
|
|
strcpy(command,"getproperty?properties=dmcp.volume");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
}
|
|
|
|
|
strcpy(command,"setproperty?dmcp.volume=100.000000");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
if (metadata_store.player_thread_active)
|
|
|
|
|
sleep(config.scan_interval_when_active);
|
|
|
|
|
else
|
|
|
|
|
sleep(config.scan_interval_when_inactive);
|
|
|
|
|
}
|
|
|
|
|
strcpy(command,"getproperty?properties=dmcp.volume");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
}
|
|
|
|
|
strcpy(command,"setproperty?dmcp.volume=100.000000");
|
|
|
|
|
debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
|
|
|
|
|
if (response) {
|
|
|
|
|
free(response);
|
|
|
|
|
response = NULL;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
if (metadata_store.player_thread_active)
|
|
|
|
|
sleep(config.scan_interval_when_active);
|
|
|
|
|
else
|
|
|
|
|
sleep(config.scan_interval_when_inactive);
|
|
|
|
|
}
|
|
|
|
|
debug(1, "DACP monitor thread exiting -- should never happen.");
|
|
|
|
|
pthread_exit(NULL);
|
|
|
|
@@ -889,6 +920,22 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
|
|
|
|
|
|
|
|
|
void dacp_monitor_start() {
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
|
|
|
|
pthread_condattr_t attr;
|
|
|
|
|
pthread_condattr_init(&attr);
|
|
|
|
|
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
|
|
|
|
|
rc = pthread_cond_init(&dacp_server_information_cv, &attr);
|
|
|
|
|
if (rc)
|
|
|
|
|
debug(1, "Error initialising the DACP Server Information Condition Variable");
|
|
|
|
|
pthread_condattr_destroy(&attr);
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef COMPILE_FOR_OSX
|
|
|
|
|
rc = pthread_cond_init(&dacp_server_information_cv, NULL);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pthread_mutexattr_t mta;
|
|
|
|
|
|
|
|
|
|
rc = pthread_mutexattr_init(&mta);
|
|
|
|
@@ -947,6 +994,8 @@ void dacp_monitor_stop() {
|
|
|
|
|
pthread_mutex_destroy(&dacp_server_information_lock);
|
|
|
|
|
debug(3, "DACP Conversation Lock Mutex Destroyed");
|
|
|
|
|
pthread_mutex_destroy(&dacp_conversation_lock);
|
|
|
|
|
pthread_cond_destroy(&dacp_server_information_cv);
|
|
|
|
|
debug(1, "DACP Server Information Condition Variable destroyed.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|