mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-24 13:13:17 -05:00
[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:
parent
804239841d
commit
08b2eb8d4c
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
159
src/player.c
159
src/player.c
@ -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, ¶m);
|
||||
|
||||
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, ¶m);
|
||||
|
||||
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;
|
||||
|
11
src/player.h
11
src/player.h
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user