mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
Merge branch 'player_listener1'
This commit is contained in:
commit
5fd79a52c5
@ -317,7 +317,7 @@ curl -X PUT "http://localhost:3689/api/player/seek?seek_ms=-30000"
|
||||
| GET | [/api/outputs](#get-a-list-of-available-outputs) | Get a list of available outputs |
|
||||
| PUT | [/api/outputs/set](#set-enabled-outputs) | Set enabled outputs |
|
||||
| GET | [/api/outputs/{id}](#get-an-output) | Get an output |
|
||||
| PUT | [/api/outputs/{id}](#change-an-output) | Change an output (enable/disable or volume) |
|
||||
| PUT | [/api/outputs/{id}](#change-an-output) | Change an output setting |
|
||||
| PUT | [/api/outputs/{id}/toggle](#toggle-an-output) | Enable or disable an output, depending on the current state |
|
||||
|
||||
|
||||
@ -482,6 +482,7 @@ PUT /api/outputs/{id}
|
||||
| --------------- | --------- | -------------------- |
|
||||
| selected | boolean | *(Optional)* `true` to enable and `false` to disable the output |
|
||||
| volume | integer | *(Optional)* Volume in percent (0 - 100) |
|
||||
| pin | string | *(Optional)* PIN for device verification |
|
||||
|
||||
**Response**
|
||||
|
||||
|
@ -1530,6 +1530,7 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
|
||||
json_object* request;
|
||||
bool selected;
|
||||
int volume;
|
||||
const char *pin;
|
||||
int ret;
|
||||
|
||||
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||
@ -1566,6 +1567,13 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
|
||||
ret = player_volume_setabs_speaker(output_id, volume);
|
||||
}
|
||||
|
||||
if (ret == 0 && jparse_contains_key(request, "pin", json_type_string))
|
||||
{
|
||||
pin = jparse_str_from_obj(request, "pin");
|
||||
if (pin)
|
||||
ret = player_speaker_authorize(output_id, pin);
|
||||
}
|
||||
|
||||
jparse_free(request);
|
||||
|
||||
if (ret < 0)
|
||||
|
@ -80,15 +80,15 @@ listener_remove(notify notify_cb)
|
||||
}
|
||||
|
||||
void
|
||||
listener_notify(enum listener_event_type type)
|
||||
listener_notify(short event_mask)
|
||||
{
|
||||
struct listener *listener;
|
||||
|
||||
listener = listener_list;
|
||||
while (listener)
|
||||
{
|
||||
if (type & listener->events)
|
||||
listener->notify_cb(type);
|
||||
if (event_mask & listener->events)
|
||||
listener->notify_cb(event_mask & listener->events);
|
||||
listener = listener->next;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ typedef void (*notify)(short event_mask);
|
||||
* Registers the given callback function to the given event types.
|
||||
* This function is not thread safe. Listeners must be added once at startup.
|
||||
*
|
||||
* @param notify_cb Callback function
|
||||
* @param notify_cb Callback function (should be a non-blocking function,
|
||||
* especially when the event is from the player)
|
||||
* @param event_mask Event mask, one or more of LISTENER_*
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
@ -57,10 +58,10 @@ listener_remove(notify notify_cb);
|
||||
* Calls the callback function of the registered listeners listening for the
|
||||
* given type of event.
|
||||
*
|
||||
* @param type The event type, on of the LISTENER_* values
|
||||
* @param event_mask Event mask, one or more of LISTENER_*
|
||||
*
|
||||
*/
|
||||
void
|
||||
listener_notify(enum listener_event_type type);
|
||||
listener_notify(short event_mask);
|
||||
|
||||
#endif /* !__LISTENER_H__ */
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
#include "transcode.h"
|
||||
#include "listener.h"
|
||||
#include "db.h"
|
||||
#include "player.h" //TODO remove me when player_pmap is removed again
|
||||
#include "worker.h"
|
||||
@ -516,7 +515,7 @@ vol_adjust(void)
|
||||
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
{
|
||||
if (device->selected && (device->volume > selected_highest))
|
||||
if (OUTPUTS_DEVICE_DISPLAY_SELECTED(device) && (device->volume > selected_highest))
|
||||
selected_highest = device->volume;
|
||||
|
||||
if (device->volume > all_highest)
|
||||
@ -527,7 +526,7 @@ vol_adjust(void)
|
||||
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
{
|
||||
if (!device->selected && (device->volume > outputs_master_volume))
|
||||
if (!OUTPUTS_DEVICE_DISPLAY_SELECTED(device) && (device->volume > outputs_master_volume))
|
||||
device->volume = outputs_master_volume;
|
||||
|
||||
device->relvol = vol_to_rel(device->volume, outputs_master_volume);
|
||||
@ -538,7 +537,7 @@ vol_adjust(void)
|
||||
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d selected %d\n", device->name, device->volume, device->relvol, device->selected);
|
||||
DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d selected %d\n", device->name, device->volume, device->relvol, OUTPUTS_DEVICE_DISPLAY_SELECTED(device));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -683,14 +682,6 @@ outputs_cb(int callback_id, uint64_t device_id, enum output_device_state state)
|
||||
event_active(outputs_deferredev, 0, 0);
|
||||
}
|
||||
|
||||
// Maybe not so great, seems it would be better if integrated into the callback
|
||||
// mechanism so that the notifications where at least deferred
|
||||
void
|
||||
outputs_listener_notify(void)
|
||||
{
|
||||
listener_notify(LISTENER_SPEAKER);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------- Called by player ---------------------------- */
|
||||
|
||||
@ -772,8 +763,6 @@ outputs_device_add(struct output_device *add, bool new_deselect)
|
||||
|
||||
device->advertised = 1;
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
@ -817,8 +806,6 @@ outputs_device_remove(struct output_device *remove)
|
||||
outputs_device_free(remove);
|
||||
|
||||
vol_adjust();
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
}
|
||||
|
||||
void
|
||||
@ -915,8 +902,6 @@ outputs_device_volume_register(struct output_device *device, int absvol, int rel
|
||||
device->volume = rel_to_vol(relvol, outputs_master_volume);
|
||||
|
||||
vol_adjust();
|
||||
|
||||
listener_notify(LISTENER_VOLUME);
|
||||
}
|
||||
|
||||
int
|
||||
@ -957,6 +942,22 @@ outputs_device_quality_set(struct output_device *device, struct media_quality *q
|
||||
return device_state_update(device, ret);
|
||||
}
|
||||
|
||||
int
|
||||
outputs_device_authorize(struct output_device *device, const char *pin, output_status_cb cb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (outputs[device->type]->disabled || !outputs[device->type]->device_authorize)
|
||||
return -1;
|
||||
|
||||
if (device->session)
|
||||
return 0; // We are already connected to the device - no auth required
|
||||
|
||||
ret = outputs[device->type]->device_authorize(device, pin, callback_add(device, cb));
|
||||
|
||||
return device_state_update(device, ret);
|
||||
}
|
||||
|
||||
void
|
||||
outputs_device_cb_set(struct output_device *device, output_status_cb cb)
|
||||
{
|
||||
@ -1099,8 +1100,6 @@ outputs_volume_set(int volume, output_status_cb cb)
|
||||
pending += ret;
|
||||
}
|
||||
|
||||
listener_notify(LISTENER_VOLUME);
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
@ -1164,16 +1163,6 @@ outputs_metadata_purge(void)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
outputs_authorize(enum output_types type, const char *pin)
|
||||
{
|
||||
if (outputs[type]->disabled)
|
||||
return;
|
||||
|
||||
if (outputs[type]->authorize)
|
||||
outputs[type]->authorize(pin);
|
||||
}
|
||||
|
||||
int
|
||||
outputs_priority(struct output_device *device)
|
||||
{
|
||||
|
@ -41,6 +41,12 @@
|
||||
// different values can only do so within a limited range (maybe max 3 secs)
|
||||
#define OUTPUTS_BUFFER_DURATION 2
|
||||
|
||||
// Whether the device should be *displayed* as selected is not given by
|
||||
// device->selected, since that means "has the user selected the device",
|
||||
// without taking into account whether it is working or available. This macro
|
||||
// is a compound of the factors that determine how to display speaker selection.
|
||||
#define OUTPUTS_DEVICE_DISPLAY_SELECTED(device) ((device)->selected && (device)->state >= OUTPUT_STATE_STOPPED && !(device)->busy && !(device)->prevent_playback)
|
||||
|
||||
// Forward declarations
|
||||
struct output_device;
|
||||
struct output_metadata;
|
||||
@ -226,6 +232,9 @@ struct output_definition
|
||||
// Request a change of quality from the device
|
||||
int (*device_quality_set)(struct output_device *device, struct media_quality *quality, int callback_id);
|
||||
|
||||
// Authorize forked-daapd to use the device
|
||||
int (*device_authorize)(struct output_device *device, const char *pin, int callback_id);
|
||||
|
||||
// Change the call back associated with a device
|
||||
void (*device_cb_set)(struct output_device *device, int callback_id);
|
||||
|
||||
@ -235,9 +244,6 @@ struct output_definition
|
||||
// Write stream data to the output devices
|
||||
void (*write)(struct output_buffer *buffer);
|
||||
|
||||
// Authorize an output with a pin-code (probably coming from the filescanner)
|
||||
void (*authorize)(const char *pin);
|
||||
|
||||
// Called from worker thread for async preparation of metadata (e.g. getting
|
||||
// artwork, which might involce downloading image data). The prepared data is
|
||||
// saved to metadata->data, which metadata_send() can use.
|
||||
@ -275,9 +281,6 @@ outputs_quality_unsubscribe(struct media_quality *quality);
|
||||
void
|
||||
outputs_cb(int callback_id, uint64_t device_id, enum output_device_state);
|
||||
|
||||
void
|
||||
outputs_listener_notify(void);
|
||||
|
||||
/* ---------------------------- Called by player ---------------------------- */
|
||||
|
||||
int
|
||||
@ -322,6 +325,9 @@ outputs_device_volume_to_pct(struct output_device *device, const char *value);
|
||||
int
|
||||
outputs_device_quality_set(struct output_device *device, struct media_quality *quality, output_status_cb cb);
|
||||
|
||||
int
|
||||
outputs_device_authorize(struct output_device *device, const char *pin, output_status_cb cb);
|
||||
|
||||
void
|
||||
outputs_device_cb_set(struct output_device *device, output_status_cb cb);
|
||||
|
||||
@ -355,9 +361,6 @@ outputs_metadata_send(uint32_t item_id, bool startup, output_metadata_finalize_c
|
||||
void
|
||||
outputs_metadata_purge(void);
|
||||
|
||||
void
|
||||
outputs_authorize(enum output_types type, const char *pin);
|
||||
|
||||
int
|
||||
outputs_priority(struct output_device *device);
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
@ -39,6 +40,10 @@
|
||||
#include "player.h"
|
||||
#include "outputs.h"
|
||||
|
||||
// If you set this to 1 you can mock device verification with the dummy output,
|
||||
// use 1234 as PIN
|
||||
#define DUMMY_DEVICE_VERIFICATION_TEST 0
|
||||
|
||||
struct dummy_session
|
||||
{
|
||||
enum output_device_state state;
|
||||
@ -97,7 +102,7 @@ dummy_status(struct dummy_session *ds)
|
||||
{
|
||||
outputs_cb(ds->callback_id, ds->device_id, ds->state);
|
||||
|
||||
if (ds->state == OUTPUT_STATE_STOPPED)
|
||||
if (ds->state <= OUTPUT_STATE_STOPPED)
|
||||
dummy_session_cleanup(ds);
|
||||
}
|
||||
|
||||
@ -113,6 +118,10 @@ dummy_device_start(struct output_device *device, int callback_id)
|
||||
if (!ds)
|
||||
return -1;
|
||||
|
||||
// Mock a denied connection
|
||||
if (device->requires_auth && !device->auth_key)
|
||||
ds->state = OUTPUT_STATE_PASSWORD;
|
||||
|
||||
dummy_status(ds);
|
||||
|
||||
return 1;
|
||||
@ -156,6 +165,10 @@ dummy_device_probe(struct output_device *device, int callback_id)
|
||||
ds->callback_id = callback_id;
|
||||
ds->state = OUTPUT_STATE_STOPPED;
|
||||
|
||||
// Mock a denied connection
|
||||
if (device->requires_auth && !device->auth_key)
|
||||
ds->state = OUTPUT_STATE_PASSWORD;
|
||||
|
||||
dummy_status(ds);
|
||||
|
||||
return 1;
|
||||
@ -175,6 +188,32 @@ dummy_device_volume_set(struct output_device *device, int callback_id)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
dummy_device_authorize(struct output_device *device, const char *pin, int callback_id)
|
||||
{
|
||||
struct dummy_session *ds;
|
||||
|
||||
ds = dummy_session_make(device, callback_id);
|
||||
if (!ds)
|
||||
return -1;
|
||||
|
||||
if (strcmp(pin, "1234") == 0)
|
||||
{
|
||||
free(device->auth_key);
|
||||
device->auth_key = strdup("test");
|
||||
ds->state = OUTPUT_STATE_STOPPED;
|
||||
}
|
||||
else
|
||||
{
|
||||
ds->state = OUTPUT_STATE_PASSWORD;
|
||||
}
|
||||
|
||||
ds->callback_id = callback_id;
|
||||
dummy_status(ds);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
dummy_device_cb_set(struct output_device *device, int callback_id)
|
||||
{
|
||||
@ -205,6 +244,7 @@ dummy_init(void)
|
||||
device->type = OUTPUT_TYPE_DUMMY;
|
||||
device->type_name = outputs_name(device->type);
|
||||
device->has_video = 0;
|
||||
device->requires_auth = DUMMY_DEVICE_VERIFICATION_TEST;
|
||||
|
||||
DPRINTF(E_INFO, L_LAUDIO, "Adding dummy output device '%s'\n", nickname);
|
||||
|
||||
@ -232,5 +272,6 @@ struct output_definition output_dummy =
|
||||
.device_flush = dummy_device_flush,
|
||||
.device_probe = dummy_device_probe,
|
||||
.device_volume_set = dummy_device_volume_set,
|
||||
.device_authorize = dummy_device_authorize,
|
||||
.device_cb_set = dummy_device_cb_set,
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2019 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||
* Copyright (C) 2012-2020 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||
* Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* RAOP AirTunes v2
|
||||
@ -152,10 +152,8 @@ enum raop_state {
|
||||
RAOP_STATE_TEARDOWN = RAOP_STATE_F_CONNECTED | 0x03,
|
||||
// Session is failed, couldn't startup or error occurred
|
||||
RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01,
|
||||
// Password issue: unknown password or bad password
|
||||
// Password issue: unknown password or bad password, or pending PIN from user
|
||||
RAOP_STATE_PASSWORD = RAOP_STATE_F_FAILED | 0x02,
|
||||
// Device requires verification, pending PIN from user
|
||||
RAOP_STATE_UNVERIFIED= RAOP_STATE_F_FAILED | 0x03,
|
||||
};
|
||||
|
||||
// Info about the device, which is not required by the player, only internally
|
||||
@ -1726,7 +1724,7 @@ raop_status(struct raop_session *rs)
|
||||
|
||||
switch (rs->state)
|
||||
{
|
||||
case RAOP_STATE_PASSWORD ... RAOP_STATE_UNVERIFIED:
|
||||
case RAOP_STATE_PASSWORD:
|
||||
state = OUTPUT_STATE_PASSWORD;
|
||||
break;
|
||||
case RAOP_STATE_FAILED:
|
||||
@ -1755,10 +1753,6 @@ raop_status(struct raop_session *rs)
|
||||
|
||||
outputs_cb(rs->callback_id, rs->device_id, state);
|
||||
rs->callback_id = -1;
|
||||
|
||||
// Ugly... fixme...
|
||||
if (rs->state == RAOP_STATE_UNVERIFIED)
|
||||
outputs_listener_notify();
|
||||
}
|
||||
|
||||
static struct raop_master_session *
|
||||
@ -1854,28 +1848,26 @@ session_free(struct raop_session *rs)
|
||||
if (!rs)
|
||||
return;
|
||||
|
||||
master_session_cleanup(rs->master_session);
|
||||
if (rs->master_session)
|
||||
master_session_cleanup(rs->master_session);
|
||||
|
||||
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
|
||||
if (rs->ctrl)
|
||||
{
|
||||
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
|
||||
evrtsp_connection_free(rs->ctrl);
|
||||
}
|
||||
|
||||
evrtsp_connection_free(rs->ctrl);
|
||||
if (rs->deferredev)
|
||||
event_free(rs->deferredev);
|
||||
|
||||
event_free(rs->deferredev);
|
||||
if (rs->server_fd >= 0)
|
||||
close(rs->server_fd);
|
||||
|
||||
close(rs->server_fd);
|
||||
|
||||
if (rs->realm)
|
||||
free(rs->realm);
|
||||
if (rs->nonce)
|
||||
free(rs->nonce);
|
||||
|
||||
if (rs->session)
|
||||
free(rs->session);
|
||||
|
||||
if (rs->address)
|
||||
free(rs->address);
|
||||
if (rs->devname)
|
||||
free(rs->devname);
|
||||
free(rs->realm);
|
||||
free(rs->nonce);
|
||||
free(rs->session);
|
||||
free(rs->address);
|
||||
free(rs->devname);
|
||||
|
||||
free(rs);
|
||||
}
|
||||
@ -1990,44 +1982,106 @@ deferredev_cb(int fd, short what, void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
static struct raop_session *
|
||||
session_make(struct output_device *rd, int family, int callback_id, bool only_probe)
|
||||
static int
|
||||
session_connection_setup(struct raop_session *rs, struct output_device *rd, int family)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
struct raop_extra *re;
|
||||
char *address;
|
||||
char *intf;
|
||||
unsigned short port;
|
||||
int ret;
|
||||
|
||||
re = rd->extra_device_info;
|
||||
|
||||
rs->sa.ss.ss_family = family;
|
||||
switch (family)
|
||||
{
|
||||
case AF_INET:
|
||||
/* We always have the v4 services, so no need to check */
|
||||
if (!rd->v4_address)
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
address = rd->v4_address;
|
||||
port = rd->v4_port;
|
||||
|
||||
rs->timing_svc = &timing_4svc;
|
||||
rs->control_svc = &control_4svc;
|
||||
|
||||
ret = inet_pton(AF_INET, address, &rs->sa.sin.sin_addr);
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0))
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
address = rd->v6_address;
|
||||
port = rd->v6_port;
|
||||
|
||||
rs->timing_svc = &timing_6svc;
|
||||
rs->control_svc = &control_6svc;
|
||||
|
||||
intf = strchr(address, '%');
|
||||
if (intf)
|
||||
*intf = '\0';
|
||||
|
||||
ret = inet_pton(AF_INET6, address, &rs->sa.sin6.sin6_addr);
|
||||
|
||||
if (intf)
|
||||
{
|
||||
*intf = '%';
|
||||
|
||||
intf++;
|
||||
|
||||
rs->sa.sin6.sin6_scope_id = if_nametoindex(intf);
|
||||
if (rs->sa.sin6.sin6_scope_id == 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not find interface %s\n", intf);
|
||||
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret <= 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Device '%s' has invalid address (%s) for %s\n", rd->name, address, (family == AF_INET) ? "ipv4" : "ipv6");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rs->ctrl = evrtsp_connection_new(address, port);
|
||||
if (!rs->ctrl)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not create control connection to '%s' (%s)\n", rd->name, address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
evrtsp_connection_set_base(rs->ctrl, evbase_player);
|
||||
|
||||
rs->address = strdup(address);
|
||||
rs->family = family;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct raop_session *
|
||||
session_make(struct output_device *rd, int callback_id, bool only_probe)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
struct raop_extra *re;
|
||||
int ret;
|
||||
|
||||
re = rd->extra_device_info;
|
||||
|
||||
|
||||
CHECK_NULL(L_RAOP, rs = calloc(1, sizeof(struct raop_session)));
|
||||
CHECK_NULL(L_RAOP, rs->deferredev = evtimer_new(evbase_player, deferredev_cb, rs));
|
||||
|
||||
rs->devname = strdup(rd->name);
|
||||
rs->volume = rd->volume;
|
||||
|
||||
rs->state = RAOP_STATE_STOPPED;
|
||||
rs->only_probe = only_probe;
|
||||
rs->reqs_in_flight = 0;
|
||||
@ -2076,77 +2130,19 @@ session_make(struct output_device *rd, int family, int callback_id, bool only_pr
|
||||
break;
|
||||
}
|
||||
|
||||
rs->ctrl = evrtsp_connection_new(address, port);
|
||||
if (!rs->ctrl)
|
||||
ret = session_connection_setup(rs, rd, AF_INET6);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not create control connection to '%s' (%s)\n", rd->name, address);
|
||||
|
||||
goto out_free_event;
|
||||
ret = session_connection_setup(rs, rd, AF_INET);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
evrtsp_connection_set_base(rs->ctrl, evbase_player);
|
||||
|
||||
rs->sa.ss.ss_family = family;
|
||||
switch (family)
|
||||
{
|
||||
case AF_INET:
|
||||
rs->timing_svc = &timing_4svc;
|
||||
rs->control_svc = &control_4svc;
|
||||
|
||||
ret = inet_pton(AF_INET, address, &rs->sa.sin.sin_addr);
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
rs->timing_svc = &timing_6svc;
|
||||
rs->control_svc = &control_6svc;
|
||||
|
||||
intf = strchr(address, '%');
|
||||
if (intf)
|
||||
*intf = '\0';
|
||||
|
||||
ret = inet_pton(AF_INET6, address, &rs->sa.sin6.sin6_addr);
|
||||
|
||||
if (intf)
|
||||
{
|
||||
*intf = '%';
|
||||
|
||||
intf++;
|
||||
|
||||
rs->sa.sin6.sin6_scope_id = if_nametoindex(intf);
|
||||
if (rs->sa.sin6.sin6_scope_id == 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not find interface %s\n", intf);
|
||||
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret <= 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Device '%s' has invalid address (%s) for %s\n", rd->name, address, (family == AF_INET) ? "ipv4" : "ipv6");
|
||||
|
||||
goto out_free_evcon;
|
||||
}
|
||||
|
||||
rs->devname = strdup(rd->name);
|
||||
rs->address = strdup(address);
|
||||
rs->family = family;
|
||||
|
||||
rs->volume = rd->volume;
|
||||
|
||||
rs->master_session = master_session_make(&rd->quality, rs->encrypt);
|
||||
if (!rs->master_session)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not attach a master session for device '%s'\n", rd->name);
|
||||
goto out_free_evcon;
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Attach to list of sessions
|
||||
@ -2158,11 +2154,8 @@ session_make(struct output_device *rd, int family, int callback_id, bool only_pr
|
||||
|
||||
return rs;
|
||||
|
||||
out_free_evcon:
|
||||
evrtsp_connection_free(rs->ctrl);
|
||||
out_free_event:
|
||||
event_free(rs->deferredev);
|
||||
free(rs);
|
||||
error:
|
||||
session_free(rs);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -3557,13 +3550,7 @@ raop_cb_pin_start(struct evrtsp_request *req, void *arg)
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
rs->state = RAOP_STATE_UNVERIFIED;
|
||||
|
||||
raop_status(rs);
|
||||
|
||||
// TODO If the user never verifies the session will remain stale
|
||||
|
||||
return;
|
||||
rs->state = RAOP_STATE_PASSWORD;
|
||||
|
||||
error:
|
||||
session_failure(rs);
|
||||
@ -4203,13 +4190,11 @@ raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg)
|
||||
|
||||
raop_send_req_options(rs, raop_cb_startup_options, "verify_step2");
|
||||
|
||||
outputs_listener_notify();
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
rs->state = RAOP_STATE_UNVERIFIED;
|
||||
raop_status(rs);
|
||||
rs->state = RAOP_STATE_PASSWORD;
|
||||
session_failure(rs);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -4239,11 +4224,11 @@ raop_cb_verification_verify_step1(struct evrtsp_request *req, void *arg)
|
||||
return;
|
||||
|
||||
error:
|
||||
rs->state = RAOP_STATE_UNVERIFIED;
|
||||
raop_status(rs);
|
||||
|
||||
verification_verify_free(rs->verification_verify_ctx);
|
||||
rs->verification_verify_ctx = NULL;
|
||||
|
||||
rs->state = RAOP_STATE_PASSWORD;
|
||||
session_failure(rs);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -4265,9 +4250,6 @@ raop_verification_verify(struct raop_session *rs)
|
||||
return 0;
|
||||
|
||||
error:
|
||||
rs->state = RAOP_STATE_UNVERIFIED;
|
||||
raop_status(rs);
|
||||
|
||||
verification_verify_free(rs->verification_verify_ctx);
|
||||
rs->verification_verify_ctx = NULL;
|
||||
return -1;
|
||||
@ -4284,20 +4266,20 @@ raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg)
|
||||
|
||||
ret = raop_verification_response_process(3, req, rs);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
goto out;
|
||||
|
||||
ret = verification_setup_result(&authorization_key, rs->verification_setup_ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", verification_setup_errmsg(rs->verification_setup_ctx));
|
||||
goto error;
|
||||
goto out;
|
||||
}
|
||||
|
||||
DPRINTF(E_LOG, L_RAOP, "Verification setup stage complete, saving authorization key\n");
|
||||
|
||||
device = outputs_device_get(rs->device_id);
|
||||
if (!device)
|
||||
goto error;
|
||||
goto out;
|
||||
|
||||
free(device->auth_key);
|
||||
device->auth_key = strdup(authorization_key);
|
||||
@ -4305,14 +4287,19 @@ raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg)
|
||||
// A blocking db call... :-~
|
||||
db_speaker_save(device);
|
||||
|
||||
// The player considers this session failed, so we don't need it any more
|
||||
session_cleanup(rs);
|
||||
// No longer RAOP_STATE_PASSWORD
|
||||
rs->state = RAOP_STATE_STOPPED;
|
||||
|
||||
/* Fallthrough */
|
||||
|
||||
error:
|
||||
out:
|
||||
verification_setup_free(rs->verification_setup_ctx);
|
||||
rs->verification_setup_ctx = NULL;
|
||||
|
||||
// Callback to player with result
|
||||
raop_status(rs);
|
||||
|
||||
// We are telling the player that the device is now stopped, so we don't need
|
||||
// the session any more
|
||||
session_cleanup(rs);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -4334,6 +4321,7 @@ raop_cb_verification_setup_step2(struct evrtsp_request *req, void *arg)
|
||||
error:
|
||||
verification_setup_free(rs->verification_setup_ctx);
|
||||
rs->verification_setup_ctx = NULL;
|
||||
session_failure(rs);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -4355,43 +4343,57 @@ raop_cb_verification_setup_step1(struct evrtsp_request *req, void *arg)
|
||||
error:
|
||||
verification_setup_free(rs->verification_setup_ctx);
|
||||
rs->verification_setup_ctx = NULL;
|
||||
session_failure(rs);
|
||||
}
|
||||
|
||||
static void
|
||||
raop_verification_setup(const char *pin)
|
||||
static int
|
||||
raop_verification_setup(struct raop_session *rs, const char *pin)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
int ret;
|
||||
|
||||
for (rs = raop_sessions; rs; rs = rs->next)
|
||||
{
|
||||
if (rs->state == RAOP_STATE_UNVERIFIED)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rs)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Got a PIN for device verification, but no device is awaiting verification\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rs->verification_setup_ctx = verification_setup_new(pin);
|
||||
if (!rs->verification_setup_ctx)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = raop_verification_request_send(1, rs, raop_cb_verification_setup_step1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
return;
|
||||
rs->state = RAOP_STATE_PASSWORD;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
verification_setup_free(rs->verification_setup_ctx);
|
||||
rs->verification_setup_ctx = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
raop_device_authorize(struct output_device *device, const char *pin, int callback_id)
|
||||
{
|
||||
struct raop_session *rs;
|
||||
int ret;
|
||||
|
||||
// Make a session so we can communicate with the device
|
||||
rs = session_make(device, callback_id, true);
|
||||
if (!rs)
|
||||
return -1;
|
||||
|
||||
ret = raop_verification_setup(rs, pin);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RAOP, "Could not send verification setup request to '%s' (address %s)\n", device->name, rs->address);
|
||||
session_cleanup(rs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
static int
|
||||
raop_verification_verify(struct raop_session *rs)
|
||||
@ -4698,26 +4700,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on
|
||||
* address and build our session URL for all subsequent requests.
|
||||
*/
|
||||
|
||||
rs = session_make(device, AF_INET6, callback_id, only_probe);
|
||||
if (rs)
|
||||
{
|
||||
if (device->auth_key)
|
||||
ret = raop_verification_verify(rs);
|
||||
else if (device->requires_auth)
|
||||
ret = raop_send_req_pin_start(rs, raop_cb_pin_start, "device_start");
|
||||
else
|
||||
ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start");
|
||||
|
||||
if (ret == 0)
|
||||
return 1;
|
||||
else
|
||||
{
|
||||
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n");
|
||||
session_cleanup(rs);
|
||||
}
|
||||
}
|
||||
|
||||
rs = session_make(device, AF_INET, callback_id, only_probe);
|
||||
rs = session_make(device, callback_id, only_probe);
|
||||
if (!rs)
|
||||
return -1;
|
||||
|
||||
@ -4730,7 +4713,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv4\n");
|
||||
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request to '%s' (address %s)\n", device->name, rs->address);
|
||||
session_cleanup(rs);
|
||||
return -1;
|
||||
}
|
||||
@ -5017,6 +5000,6 @@ struct output_definition output_raop =
|
||||
.metadata_send = raop_metadata_send,
|
||||
.metadata_purge = raop_metadata_purge,
|
||||
#ifdef RAOP_VERIFICATION
|
||||
.authorize = raop_verification_setup,
|
||||
.device_authorize = raop_device_authorize,
|
||||
#endif
|
||||
};
|
||||
|
258
src/player.c
258
src/player.c
@ -23,7 +23,7 @@
|
||||
* - handle playback commands, status checks and events from other threads
|
||||
* - receive audio from the input thread and to own the playback buffer
|
||||
* - feed the outputs at the appropriate rate (controlled by the playback timer)
|
||||
* - output device handling (partly outsourced to outputs.c)
|
||||
* - output device handling (outsourced to outputs.c)
|
||||
* - notify about playback status changes
|
||||
* - maintain the playback queue
|
||||
*
|
||||
@ -33,6 +33,15 @@
|
||||
* not always obeyed, for instance some outputs do their setup in ways that
|
||||
* could block.
|
||||
*
|
||||
* Listener events
|
||||
* ---------------
|
||||
* Events will be signaled via listener_notify(). The following rules apply to
|
||||
* how this must be done in the code:
|
||||
* - always use status_update() to make sure the callbacks that the listener
|
||||
* makes do not block the player thread, and to avoid any risk of deadlocks
|
||||
* - if the event is a result of an external command then trigger it when the
|
||||
* command is completed, so generally in a bottom half
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
@ -138,6 +147,8 @@ struct speaker_attr_param
|
||||
|
||||
bool prevent_playback;
|
||||
bool busy;
|
||||
|
||||
const char *pin;
|
||||
};
|
||||
|
||||
struct speaker_get_param
|
||||
@ -367,6 +378,18 @@ scrobble_cb(void *arg)
|
||||
}
|
||||
#endif
|
||||
|
||||
// This is just to be able to log the caller in a simple way
|
||||
#define status_update(x, y) status_update_impl((x), (y), __func__)
|
||||
static void
|
||||
status_update_impl(enum play_status status, short listener_events, const char *caller)
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "Status update - status: %d, events: %d, caller: %s\n", status, listener_events, caller);
|
||||
|
||||
player_state = status;
|
||||
|
||||
listener_notify(listener_events);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the song with the given id to the list of previously played songs
|
||||
*/
|
||||
@ -456,13 +479,6 @@ queue_item_prev(uint32_t item_id)
|
||||
return db_queue_fetch_prev(item_id, shuffle);
|
||||
}
|
||||
|
||||
static void
|
||||
status_update(enum play_status status)
|
||||
{
|
||||
player_state = status;
|
||||
|
||||
listener_notify(LISTENER_PLAYER);
|
||||
}
|
||||
|
||||
/* ------ All this is for dealing with metadata received from the input ----- */
|
||||
|
||||
@ -539,7 +555,7 @@ metadata_finalize(void *arg, int *retval)
|
||||
|
||||
outputs_metadata_send(pb_session.playing_now->item_id, false, metadata_finalize_cb);
|
||||
|
||||
status_update(player_state);
|
||||
status_update(player_state, LISTENER_PLAYER);
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -1073,7 +1089,7 @@ event_play_start()
|
||||
|
||||
session_update_play_start();
|
||||
|
||||
status_update(PLAY_PLAYING);
|
||||
status_update(PLAY_PLAYING, LISTENER_PLAYER);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1348,8 +1364,15 @@ device_add(void *arg, int *retval)
|
||||
new_deselect = (player_state == PLAY_PLAYING);
|
||||
|
||||
device = outputs_device_add(device, new_deselect);
|
||||
*retval = device ? 0 : -1;
|
||||
if (!device)
|
||||
{
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
status_update(player_state, LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
@ -1401,6 +1424,8 @@ device_remove_family(void *arg, int *retval)
|
||||
|
||||
outputs_device_free(remove);
|
||||
|
||||
status_update(player_state, LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -1409,8 +1434,23 @@ static enum command_state
|
||||
device_auth_kickoff(void *arg, int *retval)
|
||||
{
|
||||
union player_arg *cmdarg = arg;
|
||||
struct output_device *device;
|
||||
|
||||
outputs_authorize(cmdarg->auth.type, cmdarg->auth.pin);
|
||||
// First find the device requiring verification
|
||||
for (device = output_device_list; device; device = device->next)
|
||||
{
|
||||
if (device->type == cmdarg->auth.type && device->state == OUTPUT_STATE_PASSWORD)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!device)
|
||||
{
|
||||
*retval = -1;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
// We're async, so we don't care about return values or callbacks with result
|
||||
outputs_device_authorize(device, cmdarg->auth.pin, NULL);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
@ -1425,12 +1465,8 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu
|
||||
if (!device)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Output device disappeared during streaming!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_streaming_cb (status %d)\n", device->type_name, device->name, status);
|
||||
|
||||
if (status == OUTPUT_STATE_FAILED)
|
||||
else if (status == OUTPUT_STATE_FAILED)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' failed - attempting reconnect in %d sec\n", device->type_name, device->name, PLAYER_SPEAKER_RESURRECT_TIME);
|
||||
|
||||
@ -1445,11 +1481,19 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu
|
||||
DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name);
|
||||
}
|
||||
else
|
||||
outputs_device_cb_set(device, device_streaming_cb);
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_streaming_cb (status %d)\n", device->type_name, device->name, status);
|
||||
outputs_device_cb_set(device, device_streaming_cb);
|
||||
}
|
||||
|
||||
// We don't do this in the other cb's because they are triggered by a command
|
||||
// and thus the update should be done as part of the command completion (which
|
||||
// can better determine which type of listener event to use)
|
||||
status_update(player_state, LISTENER_SPEAKER);
|
||||
}
|
||||
|
||||
static void
|
||||
device_command_cb(struct output_device *device, enum output_device_state status)
|
||||
device_volume_cb(struct output_device *device, enum output_device_state status)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
@ -1457,7 +1501,7 @@ device_command_cb(struct output_device *device, enum output_device_state status)
|
||||
goto out;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_command_cb (status %d)\n", device->type_name, device->name, status);
|
||||
DPRINTF(E_DBG, L_PLAYER, "Callback from %s device %s to device_volume_cb (status %d)\n", device->type_name, device->name, status);
|
||||
|
||||
outputs_device_cb_set(device, device_streaming_cb);
|
||||
|
||||
@ -1525,7 +1569,7 @@ device_activate_cb(struct output_device *device, enum output_device_state status
|
||||
retval = commands_exec_returnvalue(cmdbase);
|
||||
if (!device)
|
||||
{
|
||||
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during startup!\n");
|
||||
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during activation!\n");
|
||||
|
||||
if (retval != -2)
|
||||
retval = -1;
|
||||
@ -1536,22 +1580,27 @@ device_activate_cb(struct output_device *device, enum output_device_state status
|
||||
|
||||
if (status == OUTPUT_STATE_PASSWORD)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' requires a valid password\n", device->type_name, device->name);
|
||||
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' requires a valid PIN or password\n", device->type_name, device->name);
|
||||
|
||||
outputs_device_deselect(device);
|
||||
|
||||
status = OUTPUT_STATE_FAILED;
|
||||
retval = -2;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status == OUTPUT_STATE_FAILED)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' failed to activate\n", device->type_name, device->name);
|
||||
|
||||
outputs_device_deselect(device);
|
||||
|
||||
if (retval != -2)
|
||||
retval = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// If we were just probing this is a no-op
|
||||
// If we were just probing or doing device verification this is a no-op, since
|
||||
// there is no session any more
|
||||
outputs_device_cb_set(device, device_streaming_cb);
|
||||
|
||||
out:
|
||||
@ -1565,8 +1614,8 @@ player_pmap(void *p)
|
||||
return "device_activate_cb";
|
||||
else if (p == device_streaming_cb)
|
||||
return "device_streaming_cb";
|
||||
else if (p == device_command_cb)
|
||||
return "device_command_cb";
|
||||
else if (p == device_volume_cb)
|
||||
return "device_volume_cb";
|
||||
else if (p == device_flush_cb)
|
||||
return "device_flush_cb";
|
||||
else if (p == device_shutdown_cb)
|
||||
@ -1693,7 +1742,7 @@ pb_session_stop(void)
|
||||
|
||||
session_stop();
|
||||
|
||||
status_update(PLAY_STOPPED);
|
||||
status_update(PLAY_STOPPED, LISTENER_PLAYER);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1788,7 +1837,7 @@ pb_suspend(void)
|
||||
|
||||
pb_timer_stop();
|
||||
|
||||
status_update(PLAY_PAUSED);
|
||||
status_update(PLAY_PAUSED, LISTENER_PLAYER);
|
||||
|
||||
seek_save();
|
||||
|
||||
@ -1897,7 +1946,7 @@ playback_stop(void *arg, int *retval)
|
||||
// Stops the input
|
||||
pb_session_stop();
|
||||
|
||||
status_update(PLAY_STOPPED);
|
||||
status_update(PLAY_STOPPED, LISTENER_PLAYER);
|
||||
|
||||
// We're async if we need to flush devices
|
||||
if (*retval > 0)
|
||||
@ -1924,7 +1973,9 @@ playback_start_bh(void *arg, int *retval)
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
status_update(PLAY_PLAYING);
|
||||
// We also ask listeners to update speaker/volume state, since it is possible
|
||||
// some of the speakers we tried to start responded with failure
|
||||
status_update(PLAY_PLAYING, LISTENER_PLAYER | LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
@ -1948,7 +1999,7 @@ playback_start_item(void *arg, int *retval)
|
||||
{
|
||||
DPRINTF(E_DBG, L_PLAYER, "Player is already playing, ignoring call to playback start\n");
|
||||
|
||||
status_update(player_state);
|
||||
status_update(player_state, LISTENER_PLAYER);
|
||||
|
||||
*retval = 1; // Value greater 0 will prevent execution of the bottom half function
|
||||
return COMMAND_END;
|
||||
@ -2339,7 +2390,7 @@ playback_pause_bh(void *arg, int *retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
status_update(PLAY_PAUSED);
|
||||
status_update(PLAY_PAUSED, LISTENER_PLAYER);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
@ -2406,11 +2457,7 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de
|
||||
spk->relvol = device->relvol;
|
||||
spk->absvol = device->volume;
|
||||
|
||||
// We can't map device->selected directly to spk->selected, since the former
|
||||
// means "has the user selected the device" (even if it isn't working or is
|
||||
// unavailable), while clients that get the latter just want to know if the
|
||||
// speaker plays or will be playing.
|
||||
spk->selected = (device->selected && device->state >= OUTPUT_STATE_STOPPED && !device->busy && !device->prevent_playback);
|
||||
spk->selected = OUTPUTS_DEVICE_DISPLAY_SELECTED(device);
|
||||
|
||||
spk->has_password = device->has_password;
|
||||
spk->has_video = device->has_video;
|
||||
@ -2570,6 +2617,13 @@ speaker_disable(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
speaker_generic_bh(void *arg, int *retval)
|
||||
{
|
||||
status_update(player_state, LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
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
|
||||
@ -2624,6 +2678,8 @@ speaker_prevent_playback_set_bh(void *arg, int *retval)
|
||||
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
|
||||
}
|
||||
else
|
||||
status_update(player_state, LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
@ -2708,10 +2764,30 @@ speaker_resurrect_bh(void *arg, int *retval)
|
||||
if (player_state == PLAY_PAUSED)
|
||||
return playback_start_bh(arg, retval);
|
||||
|
||||
status_update(player_state, LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
speaker_authorize(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;
|
||||
|
||||
*retval = outputs_device_authorize(device, param->pin, device_activate_cb);
|
||||
|
||||
if (*retval > 0)
|
||||
return COMMAND_PENDING; // async
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
volume_set(void *arg, int *retval)
|
||||
{
|
||||
@ -2720,7 +2796,7 @@ volume_set(void *arg, int *retval)
|
||||
|
||||
volume = cmdarg->intval;
|
||||
|
||||
*retval = outputs_volume_set(volume, device_command_cb);
|
||||
*retval = outputs_volume_set(volume, device_volume_cb);
|
||||
|
||||
if (*retval > 0)
|
||||
return COMMAND_PENDING; // async
|
||||
@ -2744,7 +2820,7 @@ volume_setrel_speaker(void *arg, int *retval)
|
||||
|
||||
outputs_device_volume_register(device, -1, vol_param->volume);
|
||||
|
||||
*retval = outputs_device_volume_set(device, device_command_cb);
|
||||
*retval = outputs_device_volume_set(device, device_volume_cb);
|
||||
|
||||
if (*retval > 0)
|
||||
return COMMAND_PENDING; // async
|
||||
@ -2768,7 +2844,7 @@ volume_setabs_speaker(void *arg, int *retval)
|
||||
|
||||
outputs_device_volume_register(device, vol_param->volume, -1);
|
||||
|
||||
*retval = outputs_device_volume_set(device, device_command_cb);
|
||||
*retval = outputs_device_volume_set(device, device_volume_cb);
|
||||
|
||||
if (*retval > 0)
|
||||
return COMMAND_PENDING; // async
|
||||
@ -2806,6 +2882,13 @@ volume_update_speaker(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
volume_generic_bh(void *arg, int *retval)
|
||||
{
|
||||
status_update(player_state, LISTENER_VOLUME);
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
repeat_set(void *arg, int *retval)
|
||||
{
|
||||
@ -2831,8 +2914,6 @@ repeat_set(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
listener_notify(LISTENER_OPTIONS);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2862,9 +2943,8 @@ shuffle_set(void *arg, int *retval)
|
||||
db_queue_inc_version();
|
||||
}
|
||||
|
||||
// Update shuffle mode and notify listeners
|
||||
// Update shuffle mode
|
||||
shuffle = new_shuffle;
|
||||
listener_notify(LISTENER_OPTIONS);
|
||||
|
||||
out:
|
||||
*retval = 0;
|
||||
@ -2878,12 +2958,17 @@ consume_set(void *arg, int *retval)
|
||||
|
||||
consume = cmdarg->intval;
|
||||
|
||||
listener_notify(LISTENER_OPTIONS);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
options_generic_bh(void *arg, int *retval)
|
||||
{
|
||||
status_update(player_state, LISTENER_OPTIONS);
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes all items from the history
|
||||
*/
|
||||
@ -2894,8 +2979,6 @@ playerqueue_clear_history(void *arg, int *retval)
|
||||
|
||||
cur_plversion++; // TODO [db_queue] need to update db queue version
|
||||
|
||||
listener_notify(LISTENER_QUEUE);
|
||||
|
||||
*retval = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
@ -2910,6 +2993,12 @@ playerqueue_plid(void *arg, int *retval)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
playerqueue_generic_bh(void *arg, int *retval)
|
||||
{
|
||||
status_update(player_state, LISTENER_QUEUE);
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/* ------------------------------- Player API ------------------------------- */
|
||||
|
||||
@ -2956,6 +3045,7 @@ player_playback_start(void)
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -2975,6 +3065,7 @@ player_playback_start_byitem(struct db_queue_item *queue_item)
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, playback_start_item, playback_start_bh, queue_item);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -2987,6 +3078,7 @@ player_playback_start_byid(uint32_t id)
|
||||
cmdarg.id = id;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, playback_start_id, playback_start_bh, &cmdarg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3082,9 +3174,7 @@ player_speaker_set(uint64_t *ids)
|
||||
|
||||
speaker_set_param.device_ids = ids;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, speaker_set, NULL, &speaker_set_param);
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
ret = commands_exec_sync(cmdbase, speaker_set, speaker_generic_bh, &speaker_set_param);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -3120,9 +3210,7 @@ player_speaker_enable(uint64_t id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, speaker_enable, NULL, &id);
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
ret = commands_exec_sync(cmdbase, speaker_enable, speaker_generic_bh, &id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -3132,9 +3220,7 @@ player_speaker_disable(uint64_t id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, speaker_disable, NULL, &id);
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
ret = commands_exec_sync(cmdbase, speaker_disable, speaker_generic_bh, &id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -3150,8 +3236,6 @@ player_speaker_prevent_playback_set(uint64_t id, bool 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;
|
||||
}
|
||||
|
||||
@ -3166,8 +3250,6 @@ player_speaker_busy_set(uint64_t id, bool busy)
|
||||
|
||||
ret = commands_exec_sync(cmdbase, speaker_busy_set, speaker_prevent_playback_set_bh, ¶m);
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3179,8 +3261,20 @@ player_speaker_resurrect(void *arg)
|
||||
param.device_ids = (uint64_t *)arg;
|
||||
|
||||
commands_exec_sync(cmdbase, speaker_resurrect, speaker_resurrect_bh, ¶m);
|
||||
}
|
||||
|
||||
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
|
||||
int
|
||||
player_speaker_authorize(uint64_t id, const char *pin)
|
||||
{
|
||||
struct speaker_attr_param param;
|
||||
int ret;
|
||||
|
||||
param.spk_id = id;
|
||||
param.pin = pin;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, ¶m);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
@ -3197,7 +3291,8 @@ player_volume_set(int vol)
|
||||
|
||||
cmdarg.intval = vol;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, volume_set, NULL, &cmdarg);
|
||||
ret = commands_exec_sync(cmdbase, volume_set, volume_generic_bh, &cmdarg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3216,7 +3311,8 @@ player_volume_setrel_speaker(uint64_t id, int relvol)
|
||||
vol_param.spk_id = id;
|
||||
vol_param.volume = relvol;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, volume_setrel_speaker, NULL, &vol_param);
|
||||
ret = commands_exec_sync(cmdbase, volume_setrel_speaker, volume_generic_bh, &vol_param);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3235,7 +3331,8 @@ player_volume_setabs_speaker(uint64_t id, int vol)
|
||||
vol_param.spk_id = id;
|
||||
vol_param.volume = vol;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, volume_setabs_speaker, NULL, &vol_param);
|
||||
ret = commands_exec_sync(cmdbase, volume_setabs_speaker, volume_generic_bh, &vol_param);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3248,7 +3345,8 @@ player_volume_update_speaker(uint64_t id, const char *volstr)
|
||||
vol_param.spk_id = id;
|
||||
vol_param.volstr = volstr;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, volume_update_speaker, NULL, &vol_param);
|
||||
ret = commands_exec_sync(cmdbase, volume_update_speaker, volume_generic_bh, &vol_param);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3257,7 +3355,8 @@ player_repeat_set(enum repeat_mode mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, repeat_set, NULL, &mode);
|
||||
ret = commands_exec_sync(cmdbase, repeat_set, options_generic_bh, &mode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3269,7 +3368,8 @@ player_shuffle_set(int enable)
|
||||
|
||||
cmdarg.intval = enable;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, shuffle_set, NULL, &cmdarg);
|
||||
ret = commands_exec_sync(cmdbase, shuffle_set, options_generic_bh, &cmdarg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -3281,14 +3381,15 @@ player_consume_set(int enable)
|
||||
|
||||
cmdarg.intval = enable;
|
||||
|
||||
ret = commands_exec_sync(cmdbase, consume_set, NULL, &cmdarg);
|
||||
ret = commands_exec_sync(cmdbase, consume_set, options_generic_bh, &cmdarg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
player_queue_clear_history()
|
||||
{
|
||||
commands_exec_sync(cmdbase, playerqueue_clear_history, NULL, NULL);
|
||||
commands_exec_sync(cmdbase, playerqueue_clear_history, playerqueue_generic_bh, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@ -3348,8 +3449,11 @@ player_device_remove(void *device)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
player_device_auth_kickoff(enum output_types type, char **arglist)
|
||||
|
||||
/* ----------------------- Thread: filescanner/httpd ------------------------ */
|
||||
|
||||
void
|
||||
player_raop_verification_kickoff(char **arglist)
|
||||
{
|
||||
union player_arg *cmdarg;
|
||||
|
||||
@ -3360,19 +3464,11 @@ player_device_auth_kickoff(enum output_types type, char **arglist)
|
||||
return;
|
||||
}
|
||||
|
||||
cmdarg->auth.type = type;
|
||||
cmdarg->auth.type = OUTPUT_TYPE_RAOP;
|
||||
memcpy(cmdarg->auth.pin, arglist[0], 4);
|
||||
|
||||
commands_exec_async(cmdbase, device_auth_kickoff, cmdarg);
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------- Thread: filescanner -------------------------- */
|
||||
|
||||
void
|
||||
player_raop_verification_kickoff(char **arglist)
|
||||
{
|
||||
player_device_auth_kickoff(OUTPUT_TYPE_RAOP, arglist);
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,6 +114,9 @@ player_speaker_busy_set(uint64_t id, bool busy);
|
||||
void
|
||||
player_speaker_resurrect(void *arg);
|
||||
|
||||
int
|
||||
player_speaker_authorize(uint64_t id, const char *pin);
|
||||
|
||||
int
|
||||
player_playback_start(void);
|
||||
|
||||
|
@ -140,8 +140,6 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
|
||||
struct worker_arg *cmdarg;
|
||||
void *argcpy;
|
||||
|
||||
DPRINTF(E_DBG, L_MAIN, "Got worker execute request\n");
|
||||
|
||||
cmdarg = calloc(1, sizeof(struct worker_arg));
|
||||
if (!cmdarg)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user