[dacp/player] Change support for prevent-playback and busy (ref issue #934)

Adds support for dcmp.device-busy, and also changes handling of
device-prevent-playback so it matches iTunes better.
This commit is contained in:
ejurgensen 2020-04-10 20:40:23 +02:00
parent 804239841d
commit 08b2eb8d4c
5 changed files with 179 additions and 26 deletions

View File

@ -13,6 +13,7 @@ struct dacp_prop_map;
"dmcp.volume", dacp_propget_volume, dacp_propset_volume "dmcp.volume", dacp_propget_volume, dacp_propset_volume
"dmcp.device-volume", NULL, dacp_propset_devicevolume "dmcp.device-volume", NULL, dacp_propset_devicevolume
"dmcp.device-prevent-playback", NULL, dacp_propset_devicepreventplayback "dmcp.device-prevent-playback", NULL, dacp_propset_devicepreventplayback
"dmcp.device-busy", NULL, dacp_propset_devicebusy
"dacp.playerstate", dacp_propget_playerstate, NULL "dacp.playerstate", dacp_propget_playerstate, NULL
"dacp.nowplaying", dacp_propget_nowplaying, NULL "dacp.nowplaying", dacp_propget_nowplaying, NULL
"dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime "dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime

View File

@ -117,6 +117,8 @@ dacp_propset_devicevolume(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq); dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_devicebusy(const char *value, struct httpd_request *hreq);
static void
dacp_propset_playingtime(const char *value, struct httpd_request *hreq); dacp_propset_playingtime(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_shufflestate(const char *value, struct httpd_request *hreq); dacp_propset_shufflestate(const char *value, struct httpd_request *hreq);
@ -1011,13 +1013,8 @@ dacp_propset_devicevolume(const char *value, struct httpd_request *hreq)
player_volume_update_speaker(speaker_info.id, value); player_volume_update_speaker(speaker_info.id, value);
} }
// iTunes seems to use this as way for a speaker to tell the server that it is // See player.c:speaker_prevent_playback_set() for comments regarding
// busy with something else. If the speaker makes the request with the value 1, // prevent-playback and busy properties
// then iTunes will disable the speaker, and if it is the only speaker, then
// playback will also be paused. It is not possible for the user to restart the
// speaker until it has made a request with value 0 (if attempted, iTunes will
// show it is waiting for the speaker). As you can see from the below, we
// don't fully match this behaviour, instead we just enable/disable.
static void static void
dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq) dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq)
{ {
@ -1027,13 +1024,29 @@ dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq
return; return;
if (value[0] == '1') if (value[0] == '1')
player_speaker_disable(speaker_info.id); player_speaker_prevent_playback_set(speaker_info.id, true);
else if (value[0] == '0') else if (value[0] == '0')
player_speaker_enable(speaker_info.id); player_speaker_prevent_playback_set(speaker_info.id, false);
else else
DPRINTF(E_LOG, L_DACP, "Request for setting device-prevent-playback has invalid value: '%s'\n", value); DPRINTF(E_LOG, L_DACP, "Request for setting device-prevent-playback has invalid value: '%s'\n", value);
} }
static void
dacp_propset_devicebusy(const char *value, struct httpd_request *hreq)
{
struct player_speaker_info speaker_info;
if (speaker_get(&speaker_info, hreq, "device-busy") < 0)
return;
if (value[0] == '1')
player_speaker_busy_set(speaker_info.id, true);
else if (value[0] == '0')
player_speaker_busy_set(speaker_info.id, false);
else
DPRINTF(E_LOG, L_DACP, "Request for setting device-busy has invalid value: '%s'\n", value);
}
static void static void
dacp_propset_playingtime(const char *value, struct httpd_request *hreq) dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
{ {
@ -2516,6 +2529,7 @@ dacp_reply_setproperty(struct httpd_request *hreq)
* dmcp.volume 0-100, float * dmcp.volume 0-100, float
* dmcp.device-volume -144-0, float (raop volume) * dmcp.device-volume -144-0, float (raop volume)
* dmcp.device-prevent-playback 0/1 * dmcp.device-prevent-playback 0/1
* dmcp.device-busy 0/1
*/ */
/* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */ /* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */

View File

@ -129,6 +129,8 @@ struct output_device
unsigned has_video:1; unsigned has_video:1;
unsigned requires_auth:1; unsigned requires_auth:1;
unsigned v6_disabled:1; unsigned v6_disabled:1;
unsigned prevent_playback:1;
unsigned busy:1;
// Credentials if relevant // Credentials if relevant
const char *password; const char *password;

View File

@ -112,12 +112,6 @@
//#define DEBUG_PLAYER 1 //#define DEBUG_PLAYER 1
struct volume_param {
uint64_t spk_id;
int volume;
const char *value;
};
struct spk_enum struct spk_enum
{ {
spk_enum_cb cb; spk_enum_cb cb;
@ -130,6 +124,17 @@ struct speaker_set_param
int intval; int intval;
}; };
struct speaker_attr_param
{
uint64_t spk_id;
int volume;
const char *volstr;
bool prevent_playback;
bool busy;
};
struct speaker_get_param struct speaker_get_param
{ {
uint64_t spk_id; uint64_t spk_id;
@ -376,6 +381,8 @@ static void
speaker_select_output(struct output_device *device) speaker_select_output(struct output_device *device)
{ {
device->selected = 1; device->selected = 1;
device->prevent_playback = 0;
device->busy = 0;
if (device->volume > master_volume) if (device->volume > master_volume)
{ {
@ -2402,6 +2409,8 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de
spk->has_video = device->has_video; spk->has_video = device->has_video;
spk->requires_auth = device->requires_auth; spk->requires_auth = device->requires_auth;
spk->needs_auth_key = (device->requires_auth && device->auth_key == NULL); spk->needs_auth_key = (device->requires_auth && device->auth_key == NULL);
spk->prevent_playback = device->prevent_playback;
spk->busy = device->busy;
} }
static enum command_state static enum command_state
@ -2627,6 +2636,92 @@ speaker_disable(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
/*
* Airplay speakers can via DACP set the "busy" + "prevent-playback" properties,
* which we handle below. We try to do this like iTunes, except we need to
* unselect devices, since our clients don't understand the "grayed out" state:
*
* | Playing to 1 device | Playing to 2 devices
* device-prevent-playback=1 | Playback stops, device selected but grayed out | Playback stops on device, continues on other device, device selected but grayed out
* device-prevent-playback=0 | Playback does not resume, device not grayed | Playback resumes on device, device not grayed
* (device-busy does the same)
*
* device-prevent-playback=1 | (same) | (same)
* device-busy=1 | (no change) | (no change)
* device-prevent-playback=0 | Playback does not resume, device still grayed | Playback does not resume, device still grayed
* device-busy=0 | Playback does not resume, device not grayed | Playback resumes on device, device not grayed
* (same if vice versa, ie busy=1 first)
*
*/
static enum command_state
speaker_prevent_playback_set(void *arg, int *retval)
{
struct speaker_attr_param *param = arg;
struct output_device *device;
device = outputs_device_get(param->spk_id);
if (!device)
return COMMAND_END;
device->prevent_playback = param->prevent_playback;
DPRINTF(E_DBG, L_PLAYER, "Speaker prevent playback: '%s' (id=%" PRIu64 ")\n", device->name, device->id);
if (device->prevent_playback)
*retval = speaker_deactivate(device);
else if (!device->busy)
*retval = speaker_activate(device);
else
*retval = 0;
if (*retval > 0)
return COMMAND_PENDING; // async
return COMMAND_END;
}
static enum command_state
speaker_prevent_playback_set_bh(void *arg, int *retval)
{
struct speaker_attr_param *param = arg;
if (output_sessions == 0)
{
DPRINTF(E_INFO, L_PLAYER, "Ending playback, speaker (id=%" PRIu64 ") set 'busy' or 'prevent-playback' flag\n", param->spk_id);
pb_abort(); // TODO Would be better for the user if we paused, but we don't have a handy function for that
}
*retval = 0;
return COMMAND_END;
}
static enum command_state
speaker_busy_set(void *arg, int *retval)
{
struct speaker_attr_param *param = arg;
struct output_device *device;
device = outputs_device_get(param->spk_id);
if (!device)
return COMMAND_END;
device->busy = param->busy;
DPRINTF(E_DBG, L_PLAYER, "Speaker busy: '%s' (id=%" PRIu64 ")\n", device->name, device->id);
if (device->busy)
*retval = speaker_deactivate(device);
else if (!device->prevent_playback)
*retval = speaker_activate(device);
else
*retval = 0;
if (*retval > 0)
return COMMAND_PENDING; // async
return COMMAND_END;
}
static enum command_state static enum command_state
volume_set(void *arg, int *retval) volume_set(void *arg, int *retval)
{ {
@ -2685,7 +2780,7 @@ static void debug_print_speaker()
static enum command_state static enum command_state
volume_setrel_speaker(void *arg, int *retval) volume_setrel_speaker(void *arg, int *retval)
{ {
struct volume_param *vol_param = arg; struct speaker_attr_param *vol_param = arg;
struct output_device *device; struct output_device *device;
uint64_t id; uint64_t id;
int relvol; int relvol;
@ -2734,7 +2829,7 @@ volume_setrel_speaker(void *arg, int *retval)
static enum command_state static enum command_state
volume_setabs_speaker(void *arg, int *retval) volume_setabs_speaker(void *arg, int *retval)
{ {
struct volume_param *vol_param = arg; struct speaker_attr_param *vol_param = arg;
struct output_device *device; struct output_device *device;
uint64_t id; uint64_t id;
int volume; int volume;
@ -2791,7 +2886,7 @@ volume_setabs_speaker(void *arg, int *retval)
static enum command_state static enum command_state
volume_update_speaker(void *arg, int *retval) volume_update_speaker(void *arg, int *retval)
{ {
struct volume_param *vol_param = arg; struct speaker_attr_param *vol_param = arg;
struct output_device *device; struct output_device *device;
int volume; int volume;
@ -2802,10 +2897,10 @@ volume_update_speaker(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
volume = outputs_device_volume_to_pct(device, vol_param->value); // Only converts volume = outputs_device_volume_to_pct(device, vol_param->volstr); // Only converts
if (volume < 0) if (volume < 0)
{ {
DPRINTF(E_LOG, L_DACP, "Could not parse volume '%s' in update_volume() for speaker '%s'\n", vol_param->value, device->name); DPRINTF(E_LOG, L_DACP, "Could not parse volume '%s' in update_volume() for speaker '%s'\n", vol_param->volstr, device->name);
*retval = -1; *retval = -1;
return COMMAND_END; return COMMAND_END;
} }
@ -3158,6 +3253,38 @@ player_speaker_disable(uint64_t id)
return ret; return ret;
} }
int
player_speaker_prevent_playback_set(uint64_t id, bool prevent_playback)
{
struct speaker_attr_param param;
int ret;
param.spk_id = id;
param.prevent_playback = prevent_playback;
ret = commands_exec_sync(cmdbase, speaker_prevent_playback_set, speaker_prevent_playback_set_bh, &param);
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
return ret;
}
int
player_speaker_busy_set(uint64_t id, bool busy)
{
struct speaker_attr_param param;
int ret;
param.spk_id = id;
param.busy = busy;
ret = commands_exec_sync(cmdbase, speaker_busy_set, speaker_prevent_playback_set_bh, &param);
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
return ret;
}
int int
player_volume_set(int vol) player_volume_set(int vol)
{ {
@ -3179,7 +3306,7 @@ player_volume_set(int vol)
int int
player_volume_setrel_speaker(uint64_t id, int relvol) player_volume_setrel_speaker(uint64_t id, int relvol)
{ {
struct volume_param vol_param; struct speaker_attr_param vol_param;
int ret; int ret;
if (relvol < 0 || relvol > 100) if (relvol < 0 || relvol > 100)
@ -3198,7 +3325,7 @@ player_volume_setrel_speaker(uint64_t id, int relvol)
int int
player_volume_setabs_speaker(uint64_t id, int vol) player_volume_setabs_speaker(uint64_t id, int vol)
{ {
struct volume_param vol_param; struct speaker_attr_param vol_param;
int ret; int ret;
if (vol < 0 || vol > 100) if (vol < 0 || vol > 100)
@ -3215,13 +3342,13 @@ player_volume_setabs_speaker(uint64_t id, int vol)
} }
int int
player_volume_update_speaker(uint64_t id, const char *value) player_volume_update_speaker(uint64_t id, const char *volstr)
{ {
struct volume_param vol_param; struct speaker_attr_param vol_param;
int ret; int ret;
vol_param.spk_id = id; vol_param.spk_id = id;
vol_param.value = value; vol_param.volstr = volstr;
ret = commands_exec_sync(cmdbase, volume_update_speaker, NULL, &vol_param); ret = commands_exec_sync(cmdbase, volume_update_speaker, NULL, &vol_param);
return ret; return ret;

View File

@ -40,6 +40,9 @@ struct player_speaker_info {
bool requires_auth; bool requires_auth;
bool needs_auth_key; bool needs_auth_key;
bool prevent_playback;
bool busy;
bool has_video; bool has_video;
}; };
@ -102,6 +105,12 @@ player_speaker_enable(uint64_t id);
int int
player_speaker_disable(uint64_t id); player_speaker_disable(uint64_t id);
int
player_speaker_prevent_playback_set(uint64_t id, bool prevent_playback);
int
player_speaker_busy_set(uint64_t id, bool busy);
int int
player_playback_start(void); player_playback_start(void);
@ -136,7 +145,7 @@ int
player_volume_setabs_speaker(uint64_t id, int vol); player_volume_setabs_speaker(uint64_t id, int vol);
int int
player_volume_update_speaker(uint64_t id, const char *value); player_volume_update_speaker(uint64_t id, const char *volstr);
int int
player_repeat_set(enum repeat_mode mode); player_repeat_set(enum repeat_mode mode);