diff --git a/src/dacp_prop.gperf b/src/dacp_prop.gperf index c0f52885..ca651442 100644 --- a/src/dacp_prop.gperf +++ b/src/dacp_prop.gperf @@ -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 diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 4b41d109..5725e004 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -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 */ diff --git a/src/outputs.h b/src/outputs.h index 8d30f1ba..fef5fd0f 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -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; diff --git a/src/player.c b/src/player.c index 141222ae..a5dee1ae 100644 --- a/src/player.c +++ b/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; diff --git a/src/player.h b/src/player.h index f844c3e1..d8f33cb3 100644 --- a/src/player.h +++ b/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);