[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.device-volume", NULL, dacp_propset_devicevolume
"dmcp.device-prevent-playback", NULL, dacp_propset_devicepreventplayback
"dmcp.device-busy", NULL, dacp_propset_devicebusy
"dacp.playerstate", dacp_propget_playerstate, NULL
"dacp.nowplaying", dacp_propget_nowplaying, NULL
"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
dacp_propset_devicepreventplayback(const char *value, struct httpd_request *hreq);
static void
dacp_propset_devicebusy(const char *value, struct httpd_request *hreq);
static void
dacp_propset_playingtime(const char *value, struct httpd_request *hreq);
static void
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);
}
// iTunes seems to use this as way for a speaker to tell the server that it is
// busy with something else. If the speaker makes the request with the value 1,
// 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.
// See player.c:speaker_prevent_playback_set() for comments regarding
// prevent-playback and busy properties
static void
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;
if (value[0] == '1')
player_speaker_disable(speaker_info.id);
player_speaker_prevent_playback_set(speaker_info.id, true);
else if (value[0] == '0')
player_speaker_enable(speaker_info.id);
player_speaker_prevent_playback_set(speaker_info.id, false);
else
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
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.device-volume -144-0, float (raop volume)
* dmcp.device-prevent-playback 0/1
* dmcp.device-busy 0/1
*/
/* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */

View File

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

View File

@ -112,12 +112,6 @@
//#define DEBUG_PLAYER 1
struct volume_param {
uint64_t spk_id;
int volume;
const char *value;
};
struct spk_enum
{
spk_enum_cb cb;
@ -130,6 +124,17 @@ struct speaker_set_param
int intval;
};
struct speaker_attr_param
{
uint64_t spk_id;
int volume;
const char *volstr;
bool prevent_playback;
bool busy;
};
struct speaker_get_param
{
uint64_t spk_id;
@ -376,6 +381,8 @@ static void
speaker_select_output(struct output_device *device)
{
device->selected = 1;
device->prevent_playback = 0;
device->busy = 0;
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->requires_auth = device->requires_auth;
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
@ -2627,6 +2636,92 @@ speaker_disable(void *arg, int *retval)
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
volume_set(void *arg, int *retval)
{
@ -2685,7 +2780,7 @@ static void debug_print_speaker()
static enum command_state
volume_setrel_speaker(void *arg, int *retval)
{
struct volume_param *vol_param = arg;
struct speaker_attr_param *vol_param = arg;
struct output_device *device;
uint64_t id;
int relvol;
@ -2734,7 +2829,7 @@ volume_setrel_speaker(void *arg, int *retval)
static enum command_state
volume_setabs_speaker(void *arg, int *retval)
{
struct volume_param *vol_param = arg;
struct speaker_attr_param *vol_param = arg;
struct output_device *device;
uint64_t id;
int volume;
@ -2791,7 +2886,7 @@ volume_setabs_speaker(void *arg, int *retval)
static enum command_state
volume_update_speaker(void *arg, int *retval)
{
struct volume_param *vol_param = arg;
struct speaker_attr_param *vol_param = arg;
struct output_device *device;
int volume;
@ -2802,10 +2897,10 @@ volume_update_speaker(void *arg, int *retval)
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)
{
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;
return COMMAND_END;
}
@ -3158,6 +3253,38 @@ player_speaker_disable(uint64_t id)
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
player_volume_set(int vol)
{
@ -3179,7 +3306,7 @@ player_volume_set(int vol)
int
player_volume_setrel_speaker(uint64_t id, int relvol)
{
struct volume_param vol_param;
struct speaker_attr_param vol_param;
int ret;
if (relvol < 0 || relvol > 100)
@ -3198,7 +3325,7 @@ player_volume_setrel_speaker(uint64_t id, int relvol)
int
player_volume_setabs_speaker(uint64_t id, int vol)
{
struct volume_param vol_param;
struct speaker_attr_param vol_param;
int ret;
if (vol < 0 || vol > 100)
@ -3215,13 +3342,13 @@ player_volume_setabs_speaker(uint64_t id, int vol)
}
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;
vol_param.spk_id = id;
vol_param.value = value;
vol_param.volstr = volstr;
ret = commands_exec_sync(cmdbase, volume_update_speaker, NULL, &vol_param);
return ret;

View File

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