2010-01-29 16:39:27 -05:00
/*
2011-03-31 12:52:33 -04:00
* Copyright ( C ) 2010 - 2011 Julien BLACHE < jb @ jblache . org >
2010-01-29 16:39:27 -05:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
2010-04-24 07:00:15 -04:00
# include <unistd.h>
# include <fcntl.h>
2010-01-29 16:39:27 -05:00
# include <string.h>
# include <errno.h>
# include <sys/queue.h>
# include <sys/types.h>
# include <regex.h>
# include <stdint.h>
2010-04-04 15:21:32 -04:00
# include <inttypes.h>
2010-01-29 16:39:27 -05:00
2010-04-24 07:00:15 -04:00
# if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
# define USE_EVENTFD
# include <sys / eventfd.h>
# endif
2015-07-30 16:50:11 -04:00
# include <event2/event.h>
# include <event2/buffer.h>
2010-01-29 16:39:27 -05:00
# include "logger.h"
# include "misc.h"
# include "conffile.h"
2010-04-24 09:36:00 -04:00
# include "artwork.h"
2010-01-29 16:39:27 -05:00
# include "httpd.h"
# include "httpd_dacp.h"
2011-03-08 13:22:16 -05:00
# include "dmap_common.h"
2010-04-24 06:06:15 -04:00
# include "db.h"
2015-07-11 04:58:38 -04:00
# include "daap_query.h"
2010-04-04 14:26:12 -04:00
# include "player.h"
2015-08-08 12:02:49 -04:00
# include "queue.h"
2015-05-03 02:19:15 -04:00
# include "listener.h"
2010-01-29 16:39:27 -05:00
2010-04-24 07:00:15 -04:00
/* httpd event base, from httpd.c */
extern struct event_base * evbase_httpd ;
2010-01-29 16:39:27 -05:00
/* From httpd_daap.c */
struct daap_session ;
struct daap_session *
daap_session_find ( struct evhttp_request * req , struct evkeyvalq * query , struct evbuffer * evbuf ) ;
struct uri_map {
regex_t preg ;
char * regexp ;
void ( * handler ) ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query ) ;
} ;
struct dacp_update_request {
struct evhttp_request * req ;
struct dacp_update_request * next ;
} ;
2010-04-25 05:45:46 -04:00
typedef void ( * dacp_propget ) ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
2010-05-05 12:58:13 -04:00
typedef void ( * dacp_propset ) ( const char * value , struct evkeyvalq * query ) ;
2010-04-25 05:45:46 -04:00
struct dacp_prop_map {
char * desc ;
dacp_propget propget ;
2010-04-25 06:24:46 -04:00
dacp_propset propset ;
2010-04-25 05:45:46 -04:00
} ;
/* Forward - properties getters */
static void
dacp_propget_volume ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_volumecontrollable ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_playerstate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_shufflestate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_availableshufflestates ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_repeatstate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_availablerepeatstates ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_nowplaying ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_playingtime ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
2012-12-02 04:25:48 -05:00
static void
dacp_propget_fullscreenenabled ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_fullscreen ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_visualizerenabled ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_visualizer ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_itms_songid ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_haschapterdata ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
2013-11-07 16:45:12 -05:00
static void
dacp_propget_mediakind ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
static void
dacp_propget_extendedmediakind ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi ) ;
2012-12-02 04:25:48 -05:00
2010-04-25 06:24:46 -04:00
/* Forward - properties setters */
static void
2010-05-05 12:58:13 -04:00
dacp_propset_volume ( const char * value , struct evkeyvalq * query ) ;
2010-04-25 09:15:42 -04:00
static void
2010-05-05 12:58:13 -04:00
dacp_propset_playingtime ( const char * value , struct evkeyvalq * query ) ;
2010-04-29 12:54:06 -04:00
static void
2010-05-05 12:58:13 -04:00
dacp_propset_shufflestate ( const char * value , struct evkeyvalq * query ) ;
2010-05-01 03:04:05 -04:00
static void
2010-05-05 12:58:13 -04:00
dacp_propset_repeatstate ( const char * value , struct evkeyvalq * query ) ;
2010-05-05 13:12:30 -04:00
static void
dacp_propset_userrating ( const char * value , struct evkeyvalq * query ) ;
2010-04-25 06:24:46 -04:00
2011-03-31 12:52:33 -04:00
/* gperf static hash, dacp_prop.gperf */
# include "dacp_prop_hash.c"
2010-04-25 05:45:46 -04:00
2010-01-29 16:39:27 -05:00
2010-04-24 07:00:15 -04:00
/* Play status update */
# ifdef USE_EVENTFD
static int update_efd ;
# else
static int update_pipe [ 2 ] ;
# endif
2015-07-30 16:50:11 -04:00
static struct event * updateev ;
2010-04-24 06:06:15 -04:00
static int current_rev ;
2010-04-24 07:00:15 -04:00
/* Play status update requests */
2010-01-29 16:39:27 -05:00
static struct dacp_update_request * update_requests ;
2010-04-25 09:15:42 -04:00
/* Seek timer */
2015-07-30 16:50:11 -04:00
static struct event * seek_timer ;
2010-04-25 09:15:42 -04:00
static int seek_target ;
2010-01-29 16:39:27 -05:00
2010-04-25 04:23:09 -04:00
/* DACP helpers */
static void
dacp_nowplaying ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
2015-07-31 08:43:22 -04:00
uint32_t id ;
int64_t songalbumid ;
2010-04-25 04:23:09 -04:00
if ( ( status - > status = = PLAY_STOPPED ) | | ! mfi )
return ;
2015-07-31 08:43:22 -04:00
/* Send bogus id's if playing internet radio, because clients like
* Remote and Retune will only update metadata ( like artwork ) if the id ' s
* change ( which they wouldn ' t do if we sent the real ones )
* FIXME : Giving the client invalid ids on purpose is hardly ideal , but the
* clients don ' t seem to use these ids for anything other than rating .
*/
2015-08-04 16:33:32 -04:00
if ( mfi - > data_kind = = DATA_KIND_HTTP )
2015-07-31 08:43:22 -04:00
{
id = djb_hash ( mfi - > album , strlen ( mfi - > album ) ) ;
songalbumid = ( int64_t ) id ;
}
else
{
id = status - > id ;
songalbumid = mfi - > songalbumid ;
}
2013-11-17 17:15:50 -05:00
dmap_add_container ( evbuf , " canp " , 16 ) ;
dmap_add_raw_uint32 ( evbuf , 1 ) ; /* Database */
dmap_add_raw_uint32 ( evbuf , status - > plid ) ;
dmap_add_raw_uint32 ( evbuf , status - > pos_pl ) ;
2015-07-31 08:43:22 -04:00
dmap_add_raw_uint32 ( evbuf , id ) ;
2010-04-25 04:23:09 -04:00
dmap_add_string ( evbuf , " cann " , mfi - > title ) ;
dmap_add_string ( evbuf , " cana " , mfi - > artist ) ;
dmap_add_string ( evbuf , " canl " , mfi - > album ) ;
dmap_add_string ( evbuf , " cang " , mfi - > genre ) ;
2015-07-31 08:43:22 -04:00
dmap_add_long ( evbuf , " asai " , songalbumid ) ;
2010-04-25 04:23:09 -04:00
dmap_add_int ( evbuf , " cmmk " , 1 ) ;
}
static void
dacp_playingtime ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
if ( ( status - > status = = PLAY_STOPPED ) | | ! mfi )
return ;
2015-04-11 16:38:33 -04:00
if ( mfi - > song_length )
dmap_add_int ( evbuf , " cant " , mfi - > song_length - status - > pos_ms ) ; /* Remaining time in ms */
else
dmap_add_int ( evbuf , " cant " , 0 ) ; /* Unknown remaining time */
2010-04-25 04:23:09 -04:00
dmap_add_int ( evbuf , " cast " , mfi - > song_length ) ; /* Song length in ms */
}
2010-01-29 16:39:27 -05:00
/* Update requests helpers */
2010-04-24 06:06:15 -04:00
static int
make_playstatusupdate ( struct evbuffer * evbuf )
{
struct player_status status ;
struct media_file_info * mfi ;
struct evbuffer * psu ;
int ret ;
psu = evbuffer_new ( ) ;
if ( ! psu )
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate evbuffer for playstatusupdate \n " ) ;
return - 1 ;
}
player_get_status ( & status ) ;
if ( status . status ! = PLAY_STOPPED )
{
mfi = db_file_fetch_byid ( status . id ) ;
if ( ! mfi )
{
DPRINTF ( E_LOG , L_DACP , " Could not fetch file id %d \n " , status . id ) ;
return - 1 ;
}
}
else
mfi = NULL ;
dmap_add_int ( psu , " mstt " , 200 ) ; /* 12 */
dmap_add_int ( psu , " cmsr " , current_rev ) ; /* 12 */
2013-11-22 10:41:57 -05:00
dmap_add_char ( psu , " caps " , status . status ) ; /* 9 */ /* play status, 2 = stopped, 3 = paused, 4 = playing */
dmap_add_char ( psu , " cash " , status . shuffle ) ; /* 9 */ /* shuffle, true/false */
dmap_add_char ( psu , " carp " , status . repeat ) ; /* 9 */ /* repeat, 0 = off, 1 = repeat song, 2 = repeat (playlist) */
dmap_add_char ( psu , " cafs " , 0 ) ; /* 9 */ /* dacp.fullscreen */
dmap_add_char ( psu , " cavs " , 0 ) ; /* 9 */ /* dacp.visualizer */
dmap_add_char ( psu , " cavc " , 1 ) ; /* 9 */ /* volume controllable */
dmap_add_int ( psu , " caas " , 2 ) ; /* 12 */ /* available shuffle states */
dmap_add_int ( psu , " caar " , 6 ) ; /* 12 */ /* available repeat states */
dmap_add_char ( psu , " cafe " , 0 ) ; /* 9 */ /* dacp.fullscreenenabled */
dmap_add_char ( psu , " cave " , 0 ) ; /* 9 */ /* dacp.visualizerenabled */
2010-04-24 06:06:15 -04:00
if ( mfi )
{
2010-04-25 04:23:09 -04:00
dacp_nowplaying ( psu , & status , mfi ) ;
2013-11-22 10:41:57 -05:00
dmap_add_int ( psu , " casa " , 1 ) ; /* 12 */ /* unknown */
dmap_add_int ( psu , " astm " , mfi - > song_length ) ;
dmap_add_char ( psu , " casc " , 1 ) ; /* Maybe an indication of extra data? */
dmap_add_char ( psu , " caks " , 6 ) ; /* Unknown */
2010-04-25 04:23:09 -04:00
dacp_playingtime ( psu , & status , mfi ) ;
2010-04-24 06:06:15 -04:00
free_mfi ( mfi , 0 ) ;
}
2013-11-22 10:41:57 -05:00
dmap_add_char ( psu , " casu " , 1 ) ; /* 9 */ /* unknown */
dmap_add_char ( psu , " ceQu " , 0 ) ; /* 9 */ /* unknown */
2010-04-24 06:06:15 -04:00
dmap_add_container ( evbuf , " cmst " , EVBUFFER_LENGTH ( psu ) ) ; /* 8 + len */
ret = evbuffer_add_buffer ( evbuf , psu ) ;
evbuffer_free ( psu ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not add status data to playstatusupdate reply \n " ) ;
return - 1 ;
}
return 0 ;
}
2010-01-29 16:39:27 -05:00
2010-04-24 07:00:15 -04:00
static void
playstatusupdate_cb ( int fd , short what , void * arg )
{
struct dacp_update_request * ur ;
struct evbuffer * evbuf ;
struct evbuffer * update ;
2014-05-29 17:22:00 -04:00
struct evhttp_connection * evcon ;
2010-04-24 07:00:15 -04:00
int ret ;
# ifdef USE_EVENTFD
eventfd_t count ;
ret = eventfd_read ( update_efd , & count ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not read playstatusupdate event counter: %s \n " , strerror ( errno ) ) ;
goto readd ;
}
# else
int dummy ;
read ( update_pipe [ 0 ] , & dummy , sizeof ( dummy ) ) ;
# endif
if ( ! update_requests )
goto readd ;
evbuf = evbuffer_new ( ) ;
if ( ! evbuf )
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate evbuffer for playstatusupdate reply \n " ) ;
goto readd ;
}
update = evbuffer_new ( ) ;
2010-09-12 11:31:31 -04:00
if ( ! update )
2010-04-24 07:00:15 -04:00
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate evbuffer for playstatusupdate data \n " ) ;
goto out_free_evbuf ;
}
ret = make_playstatusupdate ( update ) ;
if ( ret < 0 )
goto out_free_update ;
for ( ur = update_requests ; update_requests ; ur = update_requests )
{
update_requests = ur - > next ;
2014-05-29 17:22:00 -04:00
evcon = evhttp_request_get_connection ( ur - > req ) ;
if ( evcon )
evhttp_connection_set_closecb ( evcon , NULL , NULL ) ;
2010-04-24 07:00:15 -04:00
evbuffer_add ( evbuf , EVBUFFER_DATA ( update ) , EVBUFFER_LENGTH ( update ) ) ;
2010-05-03 12:19:41 -04:00
httpd_send_reply ( ur - > req , HTTP_OK , " OK " , evbuf ) ;
2010-04-24 07:00:15 -04:00
free ( ur ) ;
}
current_rev + + ;
out_free_update :
evbuffer_free ( update ) ;
out_free_evbuf :
evbuffer_free ( evbuf ) ;
readd :
2015-07-30 16:50:11 -04:00
ret = event_add ( updateev , NULL ) ;
2010-04-24 07:00:15 -04:00
if ( ret < 0 )
DPRINTF ( E_LOG , L_DACP , " Couldn't re-add event for playstatusupdate \n " ) ;
}
2010-09-12 08:31:41 -04:00
/* Thread: player */
static void
2015-05-03 02:19:15 -04:00
dacp_playstatus_update_handler ( enum listener_event_type type )
2010-09-12 08:31:41 -04:00
{
int ret ;
2015-05-03 02:19:15 -04:00
// Only send status update on player change events
if ( type ! = LISTENER_PLAYER )
return ;
2010-09-12 08:31:41 -04:00
# ifdef USE_EVENTFD
ret = eventfd_write ( update_efd , 1 ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_DACP , " Could not send status update event: %s \n " , strerror ( errno ) ) ;
# else
int dummy = 42 ;
ret = write ( update_pipe [ 1 ] , & dummy , sizeof ( dummy ) ) ;
if ( ret ! = sizeof ( dummy ) )
DPRINTF ( E_LOG , L_DACP , " Could not write to status update fd: %s \n " , strerror ( errno ) ) ;
# endif
}
2010-01-29 16:39:27 -05:00
static void
2010-02-14 03:34:29 -05:00
update_fail_cb ( struct evhttp_connection * evcon , void * arg )
2010-01-29 16:39:27 -05:00
{
struct dacp_update_request * ur ;
struct dacp_update_request * p ;
2014-05-29 17:22:00 -04:00
struct evhttp_connection * evc ;
2010-01-29 16:39:27 -05:00
ur = ( struct dacp_update_request * ) arg ;
DPRINTF ( E_DBG , L_DACP , " Update request: client closed connection \n " ) ;
2014-05-29 17:22:00 -04:00
evc = evhttp_request_get_connection ( ur - > req ) ;
if ( evc )
evhttp_connection_set_closecb ( evc , NULL , NULL ) ;
2010-02-14 03:34:29 -05:00
2010-01-29 16:39:27 -05:00
if ( ur = = update_requests )
update_requests = ur - > next ;
else
{
for ( p = update_requests ; p & & ( p - > next ! = ur ) ; p = p - > next )
;
if ( ! p )
{
DPRINTF ( E_LOG , L_DACP , " WARNING: struct dacp_update_request not found in list; BUG! \n " ) ;
return ;
}
p - > next = ur - > next ;
}
free ( ur ) ;
}
2010-04-25 05:45:46 -04:00
/* Properties getters */
static void
dacp_propget_volume ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_int ( evbuf , " cmvo " , status - > volume ) ;
}
static void
dacp_propget_volumecontrollable ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_char ( evbuf , " cavc " , 1 ) ;
}
static void
dacp_propget_playerstate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_char ( evbuf , " caps " , status - > status ) ;
}
static void
dacp_propget_shufflestate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_char ( evbuf , " cash " , status - > shuffle ) ;
}
static void
dacp_propget_availableshufflestates ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_int ( evbuf , " caas " , 2 ) ;
}
static void
dacp_propget_repeatstate ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_char ( evbuf , " carp " , status - > repeat ) ;
}
static void
dacp_propget_availablerepeatstates ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dmap_add_int ( evbuf , " caar " , 6 ) ;
}
static void
dacp_propget_nowplaying ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dacp_nowplaying ( evbuf , status , mfi ) ;
}
static void
dacp_propget_playingtime ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
dacp_playingtime ( evbuf , status , mfi ) ;
}
2012-12-02 04:25:48 -05:00
static void
dacp_propget_fullscreenenabled ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_fullscreen ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_visualizerenabled ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_visualizer ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_itms_songid ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_haschapterdata ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
2013-11-07 16:45:12 -05:00
static void
dacp_propget_mediakind ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
static void
dacp_propget_extendedmediakind ( struct evbuffer * evbuf , struct player_status * status , struct media_file_info * mfi )
{
// TODO
}
2012-12-02 04:25:48 -05:00
2010-04-25 06:24:46 -04:00
/* Properties setters */
static void
2010-05-05 12:58:13 -04:00
dacp_propset_volume ( const char * value , struct evkeyvalq * query )
2010-04-25 06:24:46 -04:00
{
2010-11-21 05:56:17 -05:00
const char * param ;
uint64_t id ;
2010-04-25 06:24:46 -04:00
int volume ;
int ret ;
ret = safe_atoi32 ( value , & volume ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " dmcp.volume argument doesn't convert to integer: %s \n " , value ) ;
return ;
}
2010-11-21 05:56:17 -05:00
param = evhttp_find_header ( query , " speaker-id " ) ;
if ( param )
{
ret = safe_atou64 ( param , & id ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid speaker ID in dmcp.volume request \n " ) ;
return ;
}
player_volume_setrel_speaker ( id , volume ) ;
return ;
}
param = evhttp_find_header ( query , " include-speaker-id " ) ;
if ( param )
{
ret = safe_atou64 ( param , & id ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid speaker ID in dmcp.volume request \n " ) ;
return ;
}
player_volume_setabs_speaker ( id , volume ) ;
return ;
}
2010-04-25 06:24:46 -04:00
player_volume_set ( volume ) ;
}
2010-04-25 09:15:42 -04:00
static void
seek_timer_cb ( int fd , short what , void * arg )
{
int ret ;
DPRINTF ( E_DBG , L_DACP , " Seek timer expired, target %d ms \n " , seek_target ) ;
ret = player_playback_seek ( seek_target ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player failed to seek to %d ms \n " , seek_target ) ;
return ;
}
ret = player_playback_start ( NULL ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_DACP , " Player returned an error for start after seek \n " ) ;
}
static void
2010-05-05 12:58:13 -04:00
dacp_propset_playingtime ( const char * value , struct evkeyvalq * query )
2010-04-25 09:15:42 -04:00
{
struct timeval tv ;
int ret ;
ret = safe_atoi32 ( value , & seek_target ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " dacp.playingtime argument doesn't convert to integer: %s \n " , value ) ;
return ;
}
evutil_timerclear ( & tv ) ;
tv . tv_usec = 200 * 1000 ;
2015-07-30 16:50:11 -04:00
evtimer_add ( seek_timer , & tv ) ;
2010-04-25 09:15:42 -04:00
}
2010-05-01 03:04:05 -04:00
static void
2010-05-05 12:58:13 -04:00
dacp_propset_shufflestate ( const char * value , struct evkeyvalq * query )
2010-05-01 03:04:05 -04:00
{
int enable ;
int ret ;
ret = safe_atoi32 ( value , & enable ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " dacp.shufflestate argument doesn't convert to integer: %s \n " , value ) ;
return ;
}
player_shuffle_set ( enable ) ;
}
2010-04-29 12:54:06 -04:00
static void
2010-05-05 12:58:13 -04:00
dacp_propset_repeatstate ( const char * value , struct evkeyvalq * query )
2010-04-29 12:54:06 -04:00
{
int mode ;
int ret ;
ret = safe_atoi32 ( value , & mode ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " dacp.repeatstate argument doesn't convert to integer: %s \n " , value ) ;
return ;
}
player_repeat_set ( mode ) ;
}
2010-05-05 13:12:30 -04:00
static void
dacp_propset_userrating ( const char * value , struct evkeyvalq * query )
{
struct media_file_info * mfi ;
const char * param ;
uint32_t itemid ;
uint32_t rating ;
int ret ;
ret = safe_atou32 ( value , & rating ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " dacp.userrating argument doesn't convert to integer: %s \n " , value ) ;
return ;
}
2015-01-26 16:37:02 -05:00
param = evhttp_find_header ( query , " item-spec " ) ; // Remote
if ( ! param )
param = evhttp_find_header ( query , " song-spec " ) ; // Retune
2010-05-05 13:12:30 -04:00
if ( ! param )
{
2015-01-26 16:37:02 -05:00
DPRINTF ( E_LOG , L_DACP , " Missing item-spec/song-spec parameter in dacp.userrating query \n " ) ;
2010-05-05 13:12:30 -04:00
return ;
}
param = strchr ( param , ' : ' ) ;
if ( ! param )
{
2015-01-26 16:37:02 -05:00
DPRINTF ( E_LOG , L_DACP , " Malformed item-spec/song-spec parameter in dacp.userrating query \n " ) ;
2010-05-05 13:12:30 -04:00
return ;
}
param + + ;
ret = safe_hextou32 ( param , & itemid ) ;
if ( ret < 0 )
{
2015-01-26 16:37:02 -05:00
DPRINTF ( E_LOG , L_DACP , " Couldn't convert item-spec/song-spec to an integer in dacp.userrating (%s) \n " , param ) ;
2010-05-05 13:12:30 -04:00
return ;
}
mfi = db_file_fetch_byid ( itemid ) ;
2015-07-31 08:43:22 -04:00
/* If no mfi, it may be because we sent an invalid nowplaying itemid. In this
* case request the real one from the player and default to that .
*/
2010-05-05 13:12:30 -04:00
if ( ! mfi )
{
2015-07-31 08:43:22 -04:00
DPRINTF ( E_WARN , L_DACP , " Invalid id %d for rating, defaulting to player id \n " , itemid ) ;
2010-05-05 13:12:30 -04:00
2015-07-31 08:43:22 -04:00
ret = player_now_playing ( & itemid ) ;
if ( ( ret < 0 ) | | ! ( mfi = db_file_fetch_byid ( itemid ) ) )
{
DPRINTF ( E_WARN , L_DACP , " Could not find an id for rating \n " ) ;
return ;
}
2010-05-05 13:12:30 -04:00
}
mfi - > rating = rating ;
2010-06-21 11:50:09 -04:00
/* We're not touching any string field in mfi, so it's safe to
* skip unicode_fixup_mfi ( ) before the update
*/
2010-05-05 13:12:30 -04:00
db_file_update ( mfi ) ;
free_mfi ( mfi , 0 ) ;
}
2010-04-25 05:45:46 -04:00
2010-01-29 16:39:27 -05:00
static void
dacp_reply_ctrlint ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
2013-11-09 17:51:36 -05:00
/* /ctrl-int */
/* If tags are added or removed container sizes should be adjusted too */
dmap_add_container ( evbuf , " caci " , 194 ) ; /* 8, unknown dacp container - size of content */
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12, dmap.status */
dmap_add_char ( evbuf , " muty " , 0 ) ; /* 9, dmap.updatetype */
dmap_add_int ( evbuf , " mtco " , 1 ) ; /* 12, dmap.specifiedtotalcount */
dmap_add_int ( evbuf , " mrco " , 1 ) ; /* 12, dmap.returnedcount */
dmap_add_container ( evbuf , " mlcl " , 141 ) ; /* 8, dmap.listing - size of content */
dmap_add_container ( evbuf , " mlit " , 133 ) ; /* 8, dmap.listingitem - size of content */
dmap_add_int ( evbuf , " miid " , 1 ) ; /* 12, dmap.itemid - database ID */
dmap_add_char ( evbuf , " cmik " , 1 ) ; /* 9, unknown */
dmap_add_int ( evbuf , " cmpr " , ( 2 < < 16 | 2 ) ) ; /* 12, dmcp.protocolversion */
dmap_add_int ( evbuf , " capr " , ( 2 < < 16 | 5 ) ) ; /* 12, dacp.protocolversion */
dmap_add_char ( evbuf , " cmsp " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " aeFR " , 0x64 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " cmsv " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " cass " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " caov " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " casu " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " ceSG " , 1 ) ; /* 9, unknown */
dmap_add_char ( evbuf , " cmrl " , 1 ) ; /* 9, unknown */
2013-11-14 17:14:58 -05:00
dmap_add_long ( evbuf , " ceSX " , ( 1 < < 1 | 1 ) ) ; /* 16, unknown dacp - lowest bit announces support for playqueue-contents/-edit */
2010-01-29 16:39:27 -05:00
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-01-29 16:39:27 -05:00
}
2015-07-11 04:58:38 -04:00
static int
find_first_song_id ( const char * query )
{
2015-08-08 12:02:49 -04:00
//TODO [refactor][performance] Unnecessary query, it is enough to extract the item id from the query string. Accessing the db to verify the item exists is not needed.
2015-07-11 04:58:38 -04:00
struct db_media_file_info dbmfi ;
struct query_params qp ;
int id ;
int ret ;
memset ( & qp , 0 , sizeof ( struct query_params ) ) ;
/* We only want the id of the first song */
qp . type = Q_ITEMS ;
qp . idx_type = I_FIRST ;
qp . sort = S_NONE ;
qp . offset = 0 ;
qp . limit = 1 ;
qp . filter = daap_query_parse_sql ( query ) ;
if ( ! qp . filter )
{
DPRINTF ( E_LOG , L_PLAYER , " Improper DAAP query! \n " ) ;
return - 1 ;
}
ret = db_query_start ( & qp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Could not start query \n " ) ;
goto no_query_start ;
}
if ( ( ( ret = db_query_fetch_file ( & qp , & dbmfi ) ) = = 0 ) & & ( dbmfi . id ) )
{
ret = safe_atoi32 ( dbmfi . id , & id ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Invalid song id in query result! \n " ) ;
goto no_result ;
}
DPRINTF ( E_DBG , L_PLAYER , " Found index song (id %d) \n " , id ) ;
ret = 1 ;
}
else
{
DPRINTF ( E_LOG , L_PLAYER , " No song matches query (results %d): %s \n " , qp . results , qp . filter ) ;
goto no_result ;
}
no_result :
db_query_end ( & qp ) ;
no_query_start :
if ( qp . filter )
free ( qp . filter ) ;
if ( ret = = 1 )
return id ;
else
return - 1 ;
}
2015-07-11 05:02:10 -04:00
static int
2015-08-08 12:02:49 -04:00
make_queue_for_query ( struct queue_item * * head , const char * query , const char * queuefilter , const char * sort , int quirk )
2015-07-11 04:58:38 -04:00
{
struct media_file_info * mfi ;
struct query_params qp ;
2015-08-08 12:02:49 -04:00
struct queue_item * items ;
2015-07-11 04:58:38 -04:00
int64_t albumid ;
int64_t artistid ;
int plid ;
int id ;
int idx ;
int ret ;
int len ;
char * s ;
char buf [ 1024 ] ;
if ( query )
{
id = find_first_song_id ( query ) ;
if ( id < 0 )
return - 1 ;
}
else
id = 0 ;
memset ( & qp , 0 , sizeof ( struct query_params ) ) ;
qp . offset = 0 ;
qp . limit = 0 ;
qp . sort = S_NONE ;
qp . idx_type = I_NONE ;
if ( quirk )
{
qp . sort = S_ALBUM ;
qp . type = Q_ITEMS ;
mfi = db_file_fetch_byid ( id ) ;
if ( ! mfi )
return - 1 ;
snprintf ( buf , sizeof ( buf ) , " f.songalbumid = % " PRIi64 , mfi - > songalbumid ) ;
free_mfi ( mfi , 0 ) ;
qp . filter = strdup ( buf ) ;
}
else if ( queuefilter )
{
len = strlen ( queuefilter ) ;
if ( ( len > 6 ) & & ( strncmp ( queuefilter , " album: " , 6 ) = = 0 ) )
{
qp . type = Q_ITEMS ;
ret = safe_atoi64 ( strchr ( queuefilter , ' : ' ) + 1 , & albumid ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Invalid album id in queuefilter: %s \n " , queuefilter ) ;
return - 1 ;
}
snprintf ( buf , sizeof ( buf ) , " f.songalbumid = % " PRIi64 , albumid ) ;
qp . filter = strdup ( buf ) ;
}
else if ( ( len > 7 ) & & ( strncmp ( queuefilter , " artist: " , 7 ) = = 0 ) )
{
qp . type = Q_ITEMS ;
ret = safe_atoi64 ( strchr ( queuefilter , ' : ' ) + 1 , & artistid ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Invalid artist id in queuefilter: %s \n " , queuefilter ) ;
return - 1 ;
}
snprintf ( buf , sizeof ( buf ) , " f.songartistid = % " PRIi64 , artistid ) ;
qp . filter = strdup ( buf ) ;
}
else if ( ( len > 9 ) & & ( strncmp ( queuefilter , " playlist: " , 9 ) = = 0 ) )
{
qp . type = Q_PLITEMS ;
ret = safe_atoi32 ( strchr ( queuefilter , ' : ' ) + 1 , & plid ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Invalid playlist id in queuefilter: %s \n " , queuefilter ) ;
return - 1 ;
}
qp . id = plid ;
qp . filter = strdup ( " 1 = 1 " ) ;
}
else if ( ( len > 6 ) & & ( strncmp ( queuefilter , " genre: " , 6 ) = = 0 ) )
{
qp . type = Q_ITEMS ;
s = db_escape_string ( queuefilter + 6 ) ;
if ( ! s )
return - 1 ;
snprintf ( buf , sizeof ( buf ) , " f.genre = '%s' " , s ) ;
qp . filter = strdup ( buf ) ;
}
else
{
DPRINTF ( E_LOG , L_PLAYER , " Unknown queuefilter %s \n " , queuefilter ) ;
// If the queuefilter is unkown, ignore it and use the query parameter instead to build the sql query
id = 0 ;
qp . type = Q_ITEMS ;
qp . filter = daap_query_parse_sql ( query ) ;
}
}
else
{
id = 0 ;
qp . type = Q_ITEMS ;
qp . filter = daap_query_parse_sql ( query ) ;
}
if ( sort )
{
if ( strcmp ( sort , " name " ) = = 0 )
qp . sort = S_NAME ;
else if ( strcmp ( sort , " album " ) = = 0 )
qp . sort = S_ALBUM ;
else if ( strcmp ( sort , " artist " ) = = 0 )
qp . sort = S_ARTIST ;
}
2015-08-08 12:02:49 -04:00
items = queue_make ( & qp ) ;
2015-07-11 04:58:38 -04:00
if ( qp . filter )
free ( qp . filter ) ;
2015-08-08 12:02:49 -04:00
if ( items )
* head = items ;
2015-07-11 04:58:38 -04:00
else
return - 1 ;
2015-08-08 12:02:49 -04:00
// Get the position (0-based) of the first item
idx = queueitem_pos ( items , id ) ;
2015-07-11 04:58:38 -04:00
return idx ;
}
2010-04-11 05:06:02 -04:00
static void
dacp_reply_cue_play ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
2010-05-01 04:07:06 -04:00
struct player_status status ;
2015-08-08 12:02:49 -04:00
struct queue_item * items ;
2010-04-11 05:06:02 -04:00
const char * sort ;
const char * cuequery ;
const char * param ;
uint32_t id ;
2014-12-21 14:41:44 -05:00
uint32_t pos ;
2010-10-31 06:52:31 -04:00
int clear ;
2015-02-22 01:20:10 -05:00
struct player_history * history ;
int hist ;
2010-04-11 05:06:02 -04:00
int ret ;
/* /cue?command=play&query=...&sort=...&index=N */
2010-10-31 06:52:31 -04:00
param = evhttp_find_header ( query , " clear-first " ) ;
if ( param )
{
ret = safe_atoi32 ( param , & clear ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_DACP , " Invalid clear-first value in cue request \n " ) ;
else if ( clear )
{
player_playback_stop ( ) ;
player_queue_clear ( ) ;
}
}
2010-04-11 05:06:02 -04:00
cuequery = evhttp_find_header ( query , " query " ) ;
2010-05-01 04:07:06 -04:00
if ( cuequery )
2010-04-11 05:06:02 -04:00
{
2010-05-01 04:07:06 -04:00
sort = evhttp_find_header ( query , " sort " ) ;
2010-04-11 05:06:02 -04:00
2015-08-08 12:02:49 -04:00
ret = make_queue_for_query ( & items , cuequery , NULL , sort , 0 ) ;
2013-11-21 17:33:03 -05:00
if ( ret < 0 )
2010-05-01 04:07:06 -04:00
{
DPRINTF ( E_LOG , L_DACP , " Could not build song queue \n " ) ;
2010-04-11 05:06:02 -04:00
2010-05-01 04:07:06 -04:00
dmap_send_error ( req , " cacr " , " Could not build song queue " ) ;
return ;
}
2010-04-11 05:06:02 -04:00
2015-08-08 12:02:49 -04:00
player_queue_add ( items ) ;
2010-05-01 04:07:06 -04:00
}
else
2010-04-11 05:06:02 -04:00
{
2010-05-01 04:07:06 -04:00
player_get_status ( & status ) ;
2010-04-11 05:06:02 -04:00
2010-05-01 04:07:06 -04:00
if ( status . status ! = PLAY_STOPPED )
player_playback_stop ( ) ;
2010-04-11 05:06:02 -04:00
}
2010-05-01 03:05:42 -04:00
param = evhttp_find_header ( query , " dacp.shufflestate " ) ;
if ( param )
2010-05-05 12:58:13 -04:00
dacp_propset_shufflestate ( param , NULL ) ;
2010-05-01 03:05:42 -04:00
2010-04-11 05:06:02 -04:00
id = 0 ;
2014-12-21 14:41:44 -05:00
pos = 0 ;
2010-04-11 05:06:02 -04:00
param = evhttp_find_header ( query , " index " ) ;
if ( param )
{
2014-12-21 14:41:44 -05:00
ret = safe_atou32 ( param , & pos ) ;
2010-04-11 05:06:02 -04:00
if ( ret < 0 )
DPRINTF ( E_LOG , L_DACP , " Invalid index (%s) in cue request \n " , param ) ;
}
2015-02-22 01:20:10 -05:00
/* If selection was from Up Next queue or history queue (command will be playnow), then index is relative */
hist = 0 ;
2013-11-22 10:41:57 -05:00
if ( ( param = evhttp_find_header ( query , " command " ) ) & & ( strcmp ( param , " playnow " ) = = 0 ) )
2015-02-22 01:20:10 -05:00
{
/* If mode parameter is -1, the index is relative to the history queue, otherwise to the Up Next queue */
param = evhttp_find_header ( query , " mode " ) ;
if ( param & & ( strcmp ( param , " -1 " ) = = 0 ) )
{
/* Play from history queue */
hist = 1 ;
history = player_history_get ( ) ;
if ( history - > count > pos )
{
pos = ( history - > start_index + history - > count - pos - 1 ) % MAX_HISTORY_COUNT ;
id = history - > id [ pos ] ;
}
else
{
DPRINTF ( E_LOG , L_DACP , " Could not start playback from history \n " ) ;
dmap_send_error ( req , " cacr " , " Playback failed to start " ) ;
return ;
}
}
else
{
/* Play from Up Next queue */
pos + = status . pos_pl ;
}
}
/* If playing from history queue, the pos holds the id of the item to play */
if ( hist )
2015-08-08 12:02:49 -04:00
ret = player_playback_start_byitemid ( id , & id ) ;
2015-02-22 01:20:10 -05:00
else
2015-08-08 12:02:49 -04:00
ret = player_playback_start_bypos ( pos , & id ) ;
2013-11-22 10:41:57 -05:00
2010-04-11 05:06:02 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not start playback \n " ) ;
dmap_send_error ( req , " cacr " , " Playback failed to start " ) ;
return ;
}
dmap_add_container ( evbuf , " cacr " , 24 ) ; /* 8 + len */
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12 */
dmap_add_int ( evbuf , " miid " , id ) ; /* 12 */
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-04-11 05:06:02 -04:00
}
static void
dacp_reply_cue_clear ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
/* /cue?command=clear */
player_playback_stop ( ) ;
player_queue_clear ( ) ;
dmap_add_container ( evbuf , " cacr " , 24 ) ; /* 8 + len */
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12 */
dmap_add_int ( evbuf , " miid " , 0 ) ; /* 12 */
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-04-11 05:06:02 -04:00
}
2010-01-29 16:39:27 -05:00
static void
dacp_reply_cue ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2010-04-11 05:06:02 -04:00
const char * param ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-11 05:06:02 -04:00
param = evhttp_find_header ( query , " command " ) ;
if ( ! param )
{
DPRINTF ( E_DBG , L_DACP , " No command in cue request \n " ) ;
2010-01-29 16:39:27 -05:00
2010-04-11 05:06:02 -04:00
dmap_send_error ( req , " cacr " , " No command in cue request " ) ;
return ;
}
2010-01-29 16:39:27 -05:00
2010-04-11 05:06:02 -04:00
if ( strcmp ( param , " clear " ) = = 0 )
dacp_reply_cue_clear ( req , evbuf , uri , query ) ;
else if ( strcmp ( param , " play " ) = = 0 )
dacp_reply_cue_play ( req , evbuf , uri , query ) ;
else
{
DPRINTF ( E_LOG , L_DACP , " Unknown cue command %s \n " , param ) ;
dmap_send_error ( req , " cacr " , " Unknown command in cue request " ) ;
return ;
}
2010-01-29 16:39:27 -05:00
}
2010-07-31 05:42:05 -04:00
static void
dacp_reply_playspec ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct player_status status ;
2015-08-08 12:02:49 -04:00
struct queue_item * items ;
2010-07-31 05:42:05 -04:00
struct daap_session * s ;
const char * param ;
2010-07-31 06:07:51 -04:00
const char * shuffle ;
2010-07-31 05:42:05 -04:00
uint32_t plid ;
uint32_t id ;
2014-12-21 14:41:44 -05:00
uint32_t pos ;
2010-07-31 05:42:05 -04:00
int ret ;
/* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9'
2013-10-19 05:07:10 -04:00
* or ( Apple Remote when playing a Podcast )
* / ctrl - int / 1 / playspec ? database - spec = ' dmap . persistentid : 0x1 ' & container - spec = ' dmap . persistentid : 0x5 ' & item - spec = ' dmap . itemid : 0x9 '
* With our DAAP implementation , container - spec is the playlist ID and container - item - spec / item - spec is the song ID
2010-07-31 05:42:05 -04:00
*/
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-07-31 06:07:51 -04:00
/* Check for shuffle */
shuffle = evhttp_find_header ( query , " dacp.shufflestate " ) ;
2010-07-31 05:42:05 -04:00
/* Playlist ID */
param = evhttp_find_header ( query , " container-spec " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " No container-spec in playspec request \n " ) ;
goto out_fail ;
}
param = strchr ( param , ' : ' ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " Malformed container-spec parameter in playspec request \n " ) ;
goto out_fail ;
}
param + + ;
ret = safe_hextou32 ( param , & plid ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Couldn't convert container-spec to an integer in playspec (%s) \n " , param ) ;
goto out_fail ;
}
2010-07-31 06:07:51 -04:00
if ( ! shuffle )
2010-07-31 05:42:05 -04:00
{
2010-07-31 06:07:51 -04:00
/* Start song ID */
2014-03-21 18:38:14 -04:00
if ( ( param = evhttp_find_header ( query , " item-spec " ) ) )
plid = 0 ; // This is a podcast/audiobook - just play a single item, not a playlist
else if ( ! ( param = evhttp_find_header ( query , " container-item-spec " ) ) )
2010-07-31 06:07:51 -04:00
{
2013-10-19 05:07:10 -04:00
DPRINTF ( E_LOG , L_DACP , " No container-item-spec/item-spec in playspec request \n " ) ;
2010-07-31 05:42:05 -04:00
2010-07-31 06:07:51 -04:00
goto out_fail ;
}
2010-07-31 05:42:05 -04:00
2010-07-31 06:07:51 -04:00
param = strchr ( param , ' : ' ) ;
if ( ! param )
{
2013-10-19 05:07:10 -04:00
DPRINTF ( E_LOG , L_DACP , " Malformed container-item-spec/item-spec parameter in playspec request \n " ) ;
2010-07-31 05:42:05 -04:00
2010-07-31 06:07:51 -04:00
goto out_fail ;
}
param + + ;
2010-07-31 05:42:05 -04:00
2015-02-22 00:13:21 -05:00
ret = safe_hextou32 ( param , & pos ) ;
2010-07-31 06:07:51 -04:00
if ( ret < 0 )
{
2013-10-19 05:07:10 -04:00
DPRINTF ( E_LOG , L_DACP , " Couldn't convert container-item-spec/item-spec to an integer in playspec (%s) \n " , param ) ;
2010-07-31 05:42:05 -04:00
2010-07-31 06:07:51 -04:00
goto out_fail ;
}
2010-07-31 05:42:05 -04:00
}
2010-07-31 06:07:51 -04:00
else
2015-02-22 00:13:21 -05:00
pos = 0 ;
2010-07-31 05:42:05 -04:00
2015-02-22 00:13:21 -05:00
DPRINTF ( E_DBG , L_DACP , " Playspec request for playlist %d, start song id %d%s \n " , plid , pos , ( shuffle ) ? " , shuffle " : " " ) ;
2010-07-31 05:42:05 -04:00
2015-08-08 12:02:49 -04:00
items = queue_make_pl ( plid ) ; //TODO [queue] get queue-item-id or pos for first song to play (dacp) --- , &pos);
if ( ! items )
2010-07-31 05:42:05 -04:00
{
DPRINTF ( E_LOG , L_DACP , " Could not build song queue from playlist %d \n " , plid ) ;
goto out_fail ;
}
2014-12-21 14:41:44 -05:00
DPRINTF ( E_DBG , L_DACP , " Playspec start song index is %d \n " , pos ) ;
2010-07-31 05:42:05 -04:00
player_get_status ( & status ) ;
if ( status . status ! = PLAY_STOPPED )
player_playback_stop ( ) ;
player_queue_clear ( ) ;
2015-08-08 12:02:49 -04:00
player_queue_add ( items ) ;
2010-07-31 06:32:14 -04:00
player_queue_plid ( plid ) ;
2010-07-31 05:42:05 -04:00
2010-07-31 06:07:51 -04:00
if ( shuffle )
dacp_propset_shufflestate ( shuffle , NULL ) ;
2015-08-08 12:02:49 -04:00
ret = player_playback_start_bypos ( pos , & id ) ;
2010-07-31 05:42:05 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not start playback \n " ) ;
goto out_fail ;
}
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
return ;
out_fail :
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
}
2010-01-29 16:39:27 -05:00
static void
dacp_reply_pause ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-24 11:26:33 -04:00
player_playback_pause ( ) ;
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_playpause ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2013-08-30 15:50:31 -04:00
struct player_status status ;
2010-04-24 11:26:33 -04:00
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2013-08-30 15:50:31 -04:00
player_get_status ( & status ) ;
if ( status . status = = PLAY_PLAYING )
2010-04-24 11:26:33 -04:00
{
2013-08-30 15:50:31 -04:00
player_playback_pause ( ) ;
}
else
{
ret = player_playback_start ( NULL ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player returned an error for start after pause \n " ) ;
2010-04-24 11:26:33 -04:00
2013-08-30 15:50:31 -04:00
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
2010-04-24 11:26:33 -04:00
}
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_nextitem ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2010-04-24 11:52:19 -04:00
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-24 11:52:19 -04:00
ret = player_playback_next ( ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player returned an error for nextitem \n " ) ;
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
ret = player_playback_start ( NULL ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player returned an error for start after nextitem \n " ) ;
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_previtem ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2010-04-24 11:52:19 -04:00
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-24 11:52:19 -04:00
ret = player_playback_prev ( ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player returned an error for previtem \n " ) ;
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
ret = player_playback_start ( NULL ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Player returned an error for start after previtem \n " ) ;
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_beginff ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
/* TODO */
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_beginrew ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
/* TODO */
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static void
dacp_reply_playresume ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
/* TODO */
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
2014-04-19 11:18:20 -04:00
static int
playqueuecontents_add_source ( struct evbuffer * songlist , uint32_t source_id , int pos_in_queue , uint32_t plid )
2013-11-09 17:51:36 -05:00
{
2013-11-14 17:14:58 -05:00
struct evbuffer * song ;
2014-04-19 11:18:20 -04:00
struct media_file_info * mfi ;
int ret ;
song = evbuffer_new ( ) ;
if ( ! song )
2014-05-03 13:44:26 -04:00
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate song evbuffer for playqueue-contents \n " ) ;
return - 1 ;
}
2014-04-19 11:18:20 -04:00
mfi = db_file_fetch_byid ( source_id ) ;
2014-05-03 13:44:26 -04:00
if ( ! mfi )
{
DPRINTF ( E_LOG , L_DACP , " Could not fetch file id %d \n " , source_id ) ;
return - 1 ;
}
2014-04-19 11:18:20 -04:00
dmap_add_container ( song , " ceQs " , 16 ) ;
dmap_add_raw_uint32 ( song , 1 ) ; /* Database */
dmap_add_raw_uint32 ( song , plid ) ;
dmap_add_raw_uint32 ( song , 0 ) ; /* Should perhaps be playlist index? */
dmap_add_raw_uint32 ( song , mfi - > id ) ;
dmap_add_string ( song , " ceQn " , mfi - > title ) ;
dmap_add_string ( song , " ceQr " , mfi - > artist ) ;
dmap_add_string ( song , " ceQa " , mfi - > album ) ;
dmap_add_string ( song , " ceQg " , mfi - > genre ) ;
dmap_add_long ( song , " asai " , mfi - > songalbumid ) ;
dmap_add_int ( song , " cmmk " , mfi - > media_kind ) ;
dmap_add_int ( song , " casa " , 1 ) ; /* Unknown */
dmap_add_int ( song , " astm " , mfi - > song_length ) ;
dmap_add_char ( song , " casc " , 1 ) ; /* Maybe an indication of extra data? */
dmap_add_char ( song , " caks " , 6 ) ; /* Unknown */
dmap_add_int ( song , " ceQI " , pos_in_queue ) ;
dmap_add_container ( songlist , " mlit " , EVBUFFER_LENGTH ( song ) ) ;
ret = evbuffer_add_buffer ( songlist , song ) ;
evbuffer_free ( song ) ;
2014-05-03 13:44:26 -04:00
free_mfi ( mfi , 0 ) ;
2014-04-19 11:18:20 -04:00
if ( ret < 0 )
2014-05-03 13:44:26 -04:00
{
DPRINTF ( E_LOG , L_DACP , " Could not add song to songlist for playqueue-contents \n " ) ;
return ret ;
}
2014-04-19 11:18:20 -04:00
return 0 ;
}
2014-05-03 13:47:22 -04:00
static void
dacp_reply_playqueuecontents ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri ,
2014-04-19 11:18:20 -04:00
struct evkeyvalq * query )
{
struct daap_session * s ;
2013-11-14 17:14:58 -05:00
struct evbuffer * songlist ;
struct evbuffer * playlists ;
struct player_status status ;
2014-04-19 11:18:20 -04:00
struct player_history * history ;
2015-08-08 12:02:49 -04:00
struct queue_info * queue ;
2013-11-09 17:51:36 -05:00
const char * param ;
2015-07-30 16:50:11 -04:00
size_t songlist_length ;
size_t playlist_length ;
2013-11-09 17:51:36 -05:00
int span ;
2013-11-14 17:14:58 -05:00
int i ;
2013-11-22 16:05:55 -05:00
int n ;
2013-11-09 17:51:36 -05:00
int ret ;
2014-04-19 11:18:20 -04:00
int start_index ;
2013-11-09 17:51:36 -05:00
/* /ctrl-int/1/playqueue-contents?span=50&session-id=... */
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2013-11-14 17:14:58 -05:00
DPRINTF ( E_DBG , L_DACP , " Fetching playqueue contents \n " ) ;
2013-11-17 17:15:50 -05:00
span = 50 ; /* Default */
2013-11-09 17:51:36 -05:00
param = evhttp_find_header ( query , " span " ) ;
if ( param )
2014-05-03 13:47:22 -04:00
{
ret = safe_atoi32 ( param , & span ) ;
if ( ret < 0 )
2014-05-04 16:36:37 -04:00
DPRINTF ( E_LOG , L_DACP , " Invalid span value in playqueue-contents request \n " ) ;
2014-05-03 13:47:22 -04:00
}
2013-11-09 17:51:36 -05:00
2013-11-14 17:14:58 -05:00
i = 0 ;
2014-04-19 11:18:20 -04:00
n = 0 ; // count of songs in songlist
2014-05-17 08:06:50 -04:00
songlist = evbuffer_new ( ) ;
if ( ! songlist )
2014-04-19 11:18:20 -04:00
{
2014-05-17 08:06:50 -04:00
DPRINTF ( E_LOG , L_DACP , " Could not allocate songlist evbuffer for playqueue-contents \n " ) ;
2013-11-22 16:05:55 -05:00
2014-05-17 08:06:50 -04:00
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
}
2014-04-19 11:18:20 -04:00
2014-05-17 08:06:50 -04:00
/*
* If the span parameter is negativ make song list for Previously Played ,
* otherwise make song list for Up Next and begin with first song after playlist position .
*/
if ( span < 0 )
{
history = player_history_get ( ) ;
if ( abs ( span ) > history - > count )
2014-05-04 16:36:37 -04:00
{
2014-05-17 08:06:50 -04:00
start_index = history - > start_index ;
}
else
{
start_index = ( history - > start_index + history - > count - abs ( span ) ) % MAX_HISTORY_COUNT ;
}
for ( n = 0 ; n < history - > count & & n < abs ( span ) ; n + + )
{
ret = playqueuecontents_add_source ( songlist , history - > id [ ( start_index + n ) % MAX_HISTORY_COUNT ] , ( n + 1 ) , status . plid ) ;
if ( ret < 0 )
2014-05-04 16:36:37 -04:00
{
2014-05-17 08:06:50 -04:00
DPRINTF ( E_LOG , L_DACP , " Could not add song to songlist for playqueue-contents \n " ) ;
2014-05-04 16:36:37 -04:00
2014-05-17 08:06:50 -04:00
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
2014-05-04 16:36:37 -04:00
}
}
2014-05-17 08:06:50 -04:00
}
else
{
player_get_status ( & status ) ;
/* Get queue and make songlist only if playing or paused */
if ( status . status ! = PLAY_STOPPED )
2014-05-04 16:36:37 -04:00
{
2015-08-08 12:02:49 -04:00
queue = player_queue_get_bypos ( abs ( span ) ) ;
2014-12-21 14:41:44 -05:00
if ( queue )
2014-05-04 16:36:37 -04:00
{
2014-12-21 14:41:44 -05:00
i = queue - > start_pos ;
for ( n = 0 ; ( n < queue - > count ) & & ( n < abs ( span ) ) ; n + + )
2014-05-04 16:36:37 -04:00
{
2015-08-08 12:02:49 -04:00
ret = playqueuecontents_add_source ( songlist , queue - > queue [ n ] . dbmfi_id , ( n + i + 1 ) , status . plid ) ;
2014-05-04 16:36:37 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not add song to songlist for playqueue-contents \n " ) ;
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
}
}
2015-08-08 12:02:49 -04:00
queue_info_free ( queue ) ;
2014-05-04 16:36:37 -04:00
}
}
2013-11-14 17:14:58 -05:00
}
2014-05-03 13:44:26 -04:00
/* Playlists are hist, curr and main. */
2013-11-14 17:14:58 -05:00
playlists = evbuffer_new ( ) ;
2013-11-22 16:05:55 -05:00
if ( ! playlists )
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate playlists evbuffer for playqueue-contents \n " ) ;
if ( songlist )
evbuffer_free ( songlist ) ;
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
}
2013-11-14 17:14:58 -05:00
dmap_add_container ( playlists , " mlit " , 61 ) ;
dmap_add_string ( playlists , " ceQk " , " hist " ) ; /* 12 */
dmap_add_int ( playlists , " ceQi " , - 200 ) ; /* 12 */
dmap_add_int ( playlists , " ceQm " , 200 ) ; /* 12 */
dmap_add_string ( playlists , " ceQl " , " Previously Played " ) ; /* 25 = 8 + 17 */
if ( songlist )
{
dmap_add_container ( playlists , " mlit " , 36 ) ;
dmap_add_string ( playlists , " ceQk " , " curr " ) ; /* 12 */
dmap_add_int ( playlists , " ceQi " , 0 ) ; /* 12 */
dmap_add_int ( playlists , " ceQm " , 1 ) ; /* 12 */
dmap_add_container ( playlists , " mlit " , 69 ) ;
dmap_add_string ( playlists , " ceQk " , " main " ) ; /* 12 */
dmap_add_int ( playlists , " ceQi " , 1 ) ; /* 12 */
2013-11-22 16:05:55 -05:00
dmap_add_int ( playlists , " ceQm " , n ) ; /* 12 */
2013-11-14 17:14:58 -05:00
dmap_add_string ( playlists , " ceQl " , " Up Next " ) ; /* 15 = 8 + 7 */
dmap_add_string ( playlists , " ceQh " , " from Music " ) ; /* 18 = 8 + 10 */
2015-07-30 16:50:11 -04:00
songlist_length = evbuffer_get_length ( songlist ) ;
2013-11-14 17:14:58 -05:00
}
else
songlist_length = 0 ;
/* Final construction of reply */
2015-07-30 16:50:11 -04:00
playlist_length = evbuffer_get_length ( playlists ) ;
dmap_add_container ( evbuf , " ceQR " , 79 + playlist_length + songlist_length ) ; /* size of entire container */
2013-11-14 17:14:58 -05:00
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12, dmap.status */
dmap_add_int ( evbuf , " mtco " , abs ( span ) ) ; /* 12 */
2013-11-22 16:05:55 -05:00
dmap_add_int ( evbuf , " mrco " , n ) ; /* 12 */
2013-11-14 17:14:58 -05:00
dmap_add_char ( evbuf , " ceQu " , 0 ) ; /* 9 */
2015-07-30 16:50:11 -04:00
dmap_add_container ( evbuf , " mlcl " , 8 + playlist_length + songlist_length ) ; /* 8 */
dmap_add_container ( evbuf , " ceQS " , playlist_length ) ; /* 8 */
2013-11-14 17:14:58 -05:00
ret = evbuffer_add_buffer ( evbuf , playlists ) ;
evbuffer_free ( playlists ) ;
2013-11-22 16:05:55 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not add playlists to evbuffer for playqueue-contents \n " ) ;
if ( songlist )
evbuffer_free ( songlist ) ;
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
}
2013-11-14 17:14:58 -05:00
if ( songlist )
{
ret = evbuffer_add_buffer ( evbuf , songlist ) ;
evbuffer_free ( songlist ) ;
2013-11-22 16:05:55 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not add songlist to evbuffer for playqueue-contents \n " ) ;
dmap_send_error ( req , " ceQR " , " Out of memory " ) ;
return ;
}
2013-11-14 17:14:58 -05:00
}
dmap_add_char ( evbuf , " apsm " , status . shuffle ) ; /* 9, daap.playlistshufflemode - not part of mlcl container */
dmap_add_char ( evbuf , " aprm " , status . repeat ) ; /* 9, daap.playlistrepeatmode - not part of mlcl container */
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
}
2014-05-17 08:06:50 -04:00
static void
dacp_reply_playqueueedit_clear ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
const char * param ;
param = evhttp_find_header ( query , " mode " ) ;
/*
* The mode parameter contains the playlist to be cleared .
* If mode = 0x68697374 ( hex representation of the ascii string " hist " ) clear the history ,
* otherwise the current playlist .
*/
if ( strcmp ( param , " 0x68697374 " ) = = 0 )
2015-08-08 12:02:49 -04:00
player_queue_clear_history ( ) ;
else
player_queue_clear ( ) ;
2014-05-17 08:06:50 -04:00
dmap_add_container ( evbuf , " cacr " , 24 ) ; /* 8 + len */
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12 */
dmap_add_int ( evbuf , " miid " , 0 ) ; /* 12 */
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
}
2013-11-14 17:14:58 -05:00
static void
2013-11-21 17:33:03 -05:00
dacp_reply_playqueueedit_add ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
2013-11-14 17:14:58 -05:00
{
2014-04-19 03:12:58 -04:00
//?command=add&query='dmap.itemid:156'&sort=album&mode=3&session-id=100
// -> mode=3: add to playqueue position 0 (play next)
//?command=add&query='dmap.itemid:158'&sort=album&mode=0&session-id=100
// -> mode=0: add to end of playqueue
//?command=add&query='dmap.itemid:306'&queuefilter=album:6525753023700533274&sort=album&mode=1&session-id=100
// -> mode 1: stop playblack, clear playqueue, add songs to playqueue
2015-01-18 16:54:01 -05:00
//?command=add&query='dmap.itemid:2'&query-modifier=containers&sort=name&mode=2&session-id=100
// -> mode 2: stop playblack, clear playqueue, add shuffled songs from playlist=itemid to playqueue
2014-04-19 03:12:58 -04:00
2015-08-08 12:02:49 -04:00
struct queue_item * items ;
2013-11-21 17:33:03 -05:00
const char * editquery ;
const char * queuefilter ;
2014-03-30 19:53:46 -04:00
const char * querymodifier ;
2013-11-17 17:15:50 -05:00
const char * sort ;
2013-11-14 17:14:58 -05:00
const char * param ;
2014-03-30 19:53:46 -04:00
char modifiedquery [ 32 ] ;
2013-11-21 17:33:03 -05:00
uint32_t idx ;
2013-11-17 17:15:50 -05:00
int mode ;
2014-03-30 19:53:46 -04:00
int plid ;
2013-11-14 17:14:58 -05:00
int ret ;
2013-11-22 10:41:57 -05:00
int quirkyquery ;
2013-11-14 17:14:58 -05:00
2014-09-26 17:13:17 -04:00
mode = 1 ;
2013-11-21 17:33:03 -05:00
param = evhttp_find_header ( query , " mode " ) ;
if ( param )
{
ret = safe_atoi32 ( param , & mode ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid mode value in playqueue-edit request \n " ) ;
2013-11-22 10:41:57 -05:00
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
2013-11-21 17:33:03 -05:00
return ;
}
}
2014-04-19 03:12:58 -04:00
2013-11-22 16:05:55 -05:00
if ( ( mode = = 1 ) | | ( mode = = 2 ) )
2013-11-21 17:33:03 -05:00
{
player_playback_stop ( ) ;
player_queue_clear ( ) ;
}
editquery = evhttp_find_header ( query , " query " ) ;
if ( editquery )
{
2013-11-23 17:05:13 -05:00
sort = evhttp_find_header ( query , " sort " ) ;
2013-11-22 10:41:57 -05:00
2014-04-20 05:03:49 -04:00
// if sort param is missing and an album or artist is added to the queue, set sort to "album"
if ( ! sort & & ( strstr ( editquery , " daap.songalbumid: " ) | | strstr ( editquery , " daap.songartistid: " ) ) )
{
2014-04-21 15:21:40 -04:00
sort = " album " ;
2014-04-20 05:03:49 -04:00
}
2014-07-19 02:44:27 -04:00
// only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next)
queuefilter = ( mode = = 0 | | mode = = 3 | | mode = = 5 ) ? NULL : evhttp_find_header ( query , " queuefilter " ) ;
2013-11-23 17:05:13 -05:00
2014-03-30 19:53:46 -04:00
querymodifier = evhttp_find_header ( query , " query-modifier " ) ;
if ( ! querymodifier | | ( strcmp ( querymodifier , " containers " ) ! = 0 ) )
{
quirkyquery = ( mode = = 1 ) & & strstr ( editquery , " dmap.itemid: " ) & & ( ( ! queuefilter ) | | strstr ( queuefilter , " (null) " ) ) ;
2015-08-08 12:02:49 -04:00
ret = make_queue_for_query ( & items , editquery , queuefilter , sort , quirkyquery ) ;
2014-03-30 19:53:46 -04:00
}
else
{
// Modify the query: Take the id from the editquery and use it as a queuefilter playlist id
ret = safe_atoi32 ( strchr ( editquery , ' : ' ) + 1 , & plid ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid playlist id in request: %s \n " , editquery ) ;
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
return ;
}
snprintf ( modifiedquery , sizeof ( modifiedquery ) , " playlist:%d " , plid ) ;
2015-08-08 12:02:49 -04:00
ret = make_queue_for_query ( & items , NULL , modifiedquery , sort , 0 ) ;
2014-03-30 19:53:46 -04:00
}
2013-11-21 17:33:03 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not build song queue \n " ) ;
2013-11-22 10:41:57 -05:00
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
2013-11-21 17:33:03 -05:00
return ;
}
idx = ret ;
2014-04-19 03:12:58 -04:00
if ( mode = = 3 )
{
2015-08-08 12:02:49 -04:00
player_queue_add_next ( items ) ;
2014-04-19 03:12:58 -04:00
}
else
{
2015-08-08 12:02:49 -04:00
player_queue_add ( items ) ;
2014-04-19 03:12:58 -04:00
}
2013-11-21 17:33:03 -05:00
}
else
{
DPRINTF ( E_LOG , L_DACP , " Could not add song queue, DACP query missing \n " ) ;
2013-11-22 10:41:57 -05:00
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
2013-11-21 17:33:03 -05:00
return ;
}
2013-11-22 16:05:55 -05:00
if ( mode = = 2 )
{
player_shuffle_set ( 1 ) ;
idx = 0 ;
}
2013-11-21 17:33:03 -05:00
DPRINTF ( E_DBG , L_DACP , " Song queue built, playback starting at index % " PRIu32 " \n " , idx ) ;
2015-08-08 12:02:49 -04:00
ret = player_playback_start_bypos ( idx , NULL ) ;
2013-11-21 17:33:03 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not start playback \n " ) ;
2013-11-22 10:41:57 -05:00
dmap_send_error ( req , " cacr " , " Playback failed to start " ) ;
2013-11-21 17:33:03 -05:00
return ;
}
2013-11-22 10:41:57 -05:00
2014-02-13 09:46:24 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
2013-11-21 17:33:03 -05:00
}
2014-04-19 02:09:32 -04:00
static void
dacp_reply_playqueueedit_move ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
/*
* Handles the move command .
* Exampe request :
* playqueue - edit ? command = move & edit - params = ' edit - param . move - pair : 3 , 0 ' & session - id = 100
*
* The ' edit - param . move - pair ' param contains the index of the song in the playqueue to be moved ( index 3 in the example )
* and the index of the song after which it should be inserted ( index 0 in the exampe , the now playing song ) .
*/
int ret ;
2014-04-20 00:43:01 -04:00
const char * param ;
2014-04-19 02:09:32 -04:00
int src ;
int dst ;
2014-04-20 00:43:01 -04:00
param = evhttp_find_header ( query , " edit-params " ) ;
if ( param )
2014-04-19 02:09:32 -04:00
{
2014-04-20 00:43:01 -04:00
ret = safe_atoi32 ( strchr ( param , ' : ' ) + 1 , & src ) ;
2014-04-19 02:09:32 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid edit-params move-from value in playqueue-edit request \n " ) ;
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
return ;
}
2014-04-20 00:43:01 -04:00
ret = safe_atoi32 ( strchr ( param , ' , ' ) + 1 , & dst ) ;
2014-04-19 02:09:32 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid edit-params move-to value in playqueue-edit request \n " ) ;
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
return ;
}
2015-08-08 12:02:49 -04:00
player_queue_move_bypos ( src , dst ) ;
2014-04-19 02:09:32 -04:00
}
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
2014-04-19 02:35:07 -04:00
static void
dacp_reply_playqueueedit_remove ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
/*
* Handles the remove command .
* Exampe request ( removes song at position 1 in the playqueue ) :
* ? command = remove & items = 1 & session - id = 100
*/
int ret ;
2014-04-20 00:43:01 -04:00
const char * param ;
2014-04-19 02:35:07 -04:00
int item_index ;
2014-04-20 00:43:01 -04:00
param = evhttp_find_header ( query , " items " ) ;
if ( param )
2014-04-19 02:35:07 -04:00
{
2014-04-20 00:43:01 -04:00
ret = safe_atoi32 ( param , & item_index ) ;
2014-04-19 02:35:07 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid edit-params remove item value in playqueue-edit request \n " ) ;
dmap_send_error ( req , " cacr " , " Invalid request " ) ;
return ;
}
2015-08-08 12:02:49 -04:00
player_queue_remove_bypos ( item_index ) ;
2014-04-19 02:35:07 -04:00
}
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
2013-11-21 17:33:03 -05:00
static void
dacp_reply_playqueueedit ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
const char * param ;
2013-11-17 17:15:50 -05:00
/* Variations of /ctrl-int/1/playqueue-edit and expected behaviour
User selected play ( album or artist tab ) :
? command = add & query = ' . . . ' & sort = album & mode = 1 & session - id = . . .
- > clear queue , play query results
2014-03-30 19:53:46 -04:00
User selected play ( playlist ) :
? command = add & query = ' dmap . itemid : . . . ' & query - modifier = containers & mode = 1 & session - id = . . .
- > clear queue , play playlist with the id specified by itemid
2013-11-17 17:15:50 -05:00
User selected track ( album tab ) :
? command = add & query = ' dmap . itemid : . . . ' & queuefilter = album : . . . & sort = album & mode = 1 & session - id = . . .
- > clear queue , play itemid and the rest of album
2014-02-21 14:39:57 -05:00
User selected track ( artist tab ) :
? command = add & query = ' dmap . itemid : . . . ' & queuefilter = artist : . . . & sort = album & mode = 1 & session - id = . . .
- > clear queue , play itemid and the rest of artist tracks
2013-11-17 17:15:50 -05:00
User selected track ( song tab ) :
? command = add & query = ' dmap . itemid : . . . ' & queuefilter = playlist : . . . & sort = name & mode = 1 & session - id = . . .
- > clear queue , play itemid and the rest of playlist
User selected track ( playlist tab ) :
? command = add & query = ' dmap . containeritemid : . . . ' & queuefilter = playlist : . . . & sort = physical & mode = 1 & session - id = . . .
- > clear queue , play containeritemid and the rest of playlist
User selected shuffle ( artist tab ) :
? command = add & query = ' . . . ' & sort = album & mode = 2 & session - id = . . .
- > clear queue , play shuffled query results
User selected add item to queue :
? command = add & query = ' . . . ' & sort = album & mode = 0 & session - id = . . .
- > add query results to queue
User selected play next song ( album tab )
? command = add & query = ' daap . songalbumid : . . . ' & sort = album & mode = 3 & session - id = . . .
- > replace queue from after current song with query results
User selected track in queue :
? command = playnow & index = . . . & session - id = . . .
- > play index
2013-11-22 10:41:57 -05:00
2014-04-18 16:39:17 -04:00
And the quirky query from Remote - no sort and no queuefilter
User selected track ( Audiobooks ) :
2013-11-22 10:41:57 -05:00
? command = add & query = ' dmap . itemid : . . . ' & mode = 1 & session - id = . . .
2014-04-18 16:39:17 -04:00
- > clear queue , play itemid and the rest of album tracks
2014-04-19 02:09:32 -04:00
? command = move & edit - params = ' edit - param . move - pair : 3 , 0 ' & session - id = 100
- > move song from playqueue position 3 to be played after song at position 0
2014-04-19 02:35:07 -04:00
? command = remove & items = 1 & session - id = 100
- > remove song on position 1 from the playqueue
2013-11-17 17:15:50 -05:00
*/
2013-11-14 17:14:58 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2013-11-21 17:33:03 -05:00
param = evhttp_find_header ( query , " command " ) ;
if ( ! param )
2013-11-17 17:15:50 -05:00
{
2013-11-21 17:33:03 -05:00
DPRINTF ( E_LOG , L_DACP , " No command in playqueue-edit request \n " ) ;
2013-11-09 17:51:36 -05:00
2013-11-21 17:44:37 -05:00
dmap_send_error ( req , " cmst " , " Invalid request " ) ;
2013-11-21 17:33:03 -05:00
return ;
2013-11-17 17:15:50 -05:00
}
2013-11-21 17:33:03 -05:00
if ( strcmp ( param , " clear " ) = = 0 )
2014-05-17 08:06:50 -04:00
dacp_reply_playqueueedit_clear ( req , evbuf , uri , query ) ;
2013-11-21 17:33:03 -05:00
else if ( strcmp ( param , " playnow " ) = = 0 )
2013-11-22 10:41:57 -05:00
dacp_reply_cue_play ( req , evbuf , uri , query ) ;
2013-11-21 17:33:03 -05:00
else if ( strcmp ( param , " add " ) = = 0 )
dacp_reply_playqueueedit_add ( req , evbuf , uri , query ) ;
2014-04-19 02:09:32 -04:00
else if ( strcmp ( param , " move " ) = = 0 )
dacp_reply_playqueueedit_move ( req , evbuf , uri , query ) ;
2014-04-19 02:35:07 -04:00
else if ( strcmp ( param , " remove " ) = = 0 )
dacp_reply_playqueueedit_remove ( req , evbuf , uri , query ) ;
2013-11-17 17:15:50 -05:00
else
{
2013-11-21 17:33:03 -05:00
DPRINTF ( E_LOG , L_DACP , " Unknown playqueue-edit command %s \n " , param ) ;
2013-11-17 17:15:50 -05:00
2013-11-21 17:44:37 -05:00
dmap_send_error ( req , " cmst " , " Invalid request " ) ;
2013-11-17 17:15:50 -05:00
return ;
}
2013-11-09 17:51:36 -05:00
}
2010-01-29 16:39:27 -05:00
static void
dacp_reply_playstatusupdate ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
struct dacp_update_request * ur ;
2014-05-29 17:22:00 -04:00
struct evhttp_connection * evcon ;
2010-01-29 16:39:27 -05:00
const char * param ;
int reqd_rev ;
int ret ;
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
param = evhttp_find_header ( query , " revision-number " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " Missing revision-number in update request \n " ) ;
dmap_send_error ( req , " cmst " , " Invalid request " ) ;
return ;
}
2010-02-02 15:02:24 -05:00
ret = safe_atoi32 ( param , & reqd_rev ) ;
2010-01-29 16:39:27 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Parameter revision-number not an integer \n " ) ;
dmap_send_error ( req , " cmst " , " Invalid request " ) ;
return ;
}
2014-01-03 16:22:21 -05:00
if ( ( reqd_rev = = 0 ) | | ( reqd_rev = = 1 ) )
2010-01-29 16:39:27 -05:00
{
2010-04-24 06:06:15 -04:00
ret = make_playstatusupdate ( evbuf ) ;
if ( ret < 0 )
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
else
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-01-29 16:39:27 -05:00
return ;
}
/* Else, just let the request hang until we have changes to push back */
ur = ( struct dacp_update_request * ) malloc ( sizeof ( struct dacp_update_request ) ) ;
if ( ! ur )
{
DPRINTF ( E_LOG , L_DACP , " Out of memory for update request \n " ) ;
dmap_send_error ( req , " cmst " , " Out of memory " ) ;
return ;
}
ur - > req = req ;
ur - > next = update_requests ;
update_requests = ur ;
2010-02-14 03:34:29 -05:00
/* If the connection fails before we have an update to push out
* to the client , we need to know .
2010-01-29 16:39:27 -05:00
*/
2014-05-29 17:22:00 -04:00
evcon = evhttp_request_get_connection ( req ) ;
if ( evcon )
evhttp_connection_set_closecb ( evcon , update_fail_cb , ur ) ;
2010-01-29 16:39:27 -05:00
}
static void
dacp_reply_nowplayingartwork ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
2010-04-24 09:36:00 -04:00
char clen [ 32 ] ;
2010-01-29 16:39:27 -05:00
struct daap_session * s ;
2014-05-29 17:22:00 -04:00
struct evkeyvalq * headers ;
2010-04-24 09:36:00 -04:00
const char * param ;
2011-03-30 13:35:13 -04:00
char * ctype ;
2015-07-30 16:50:11 -04:00
size_t len ;
2010-04-24 09:36:00 -04:00
uint32_t id ;
int max_w ;
int max_h ;
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-24 09:36:00 -04:00
param = evhttp_find_header ( query , " mw " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " Request for artwork without mw parameter \n " ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
ret = safe_atoi32 ( param , & max_w ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not convert mw parameter to integer \n " ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
param = evhttp_find_header ( query , " mh " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " Request for artwork without mh parameter \n " ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
ret = safe_atoi32 ( param , & max_h ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not convert mh parameter to integer \n " ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
ret = player_now_playing ( & id ) ;
if ( ret < 0 )
goto no_artwork ;
2015-04-09 15:23:20 -04:00
ret = artwork_get_item ( evbuf , id , max_w , max_h ) ;
2015-07-30 16:50:11 -04:00
len = evbuffer_get_length ( evbuf ) ;
2011-03-30 13:35:13 -04:00
switch ( ret )
2010-04-24 09:36:00 -04:00
{
2011-03-30 13:35:13 -04:00
case ART_FMT_PNG :
ctype = " image/png " ;
break ;
case ART_FMT_JPEG :
ctype = " image/jpeg " ;
break ;
default :
2015-07-30 16:50:11 -04:00
if ( len > 0 )
evbuffer_drain ( evbuf , len ) ;
2010-04-24 09:36:00 -04:00
2011-03-30 13:35:13 -04:00
goto no_artwork ;
2010-04-24 09:36:00 -04:00
}
2014-05-29 17:22:00 -04:00
headers = evhttp_request_get_output_headers ( req ) ;
evhttp_remove_header ( headers , " Content-Type " ) ;
evhttp_add_header ( headers , " Content-Type " , ctype ) ;
2015-07-30 16:50:11 -04:00
snprintf ( clen , sizeof ( clen ) , " %ld " , ( long ) len ) ;
2014-05-29 17:22:00 -04:00
evhttp_add_header ( headers , " Content-Length " , clen ) ;
2010-04-24 09:36:00 -04:00
2010-05-03 12:19:41 -04:00
/* No gzip compression for artwork */
2010-04-24 09:36:00 -04:00
evhttp_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
return ;
no_artwork :
2010-01-29 16:39:27 -05:00
evhttp_send_error ( req , HTTP_NOTFOUND , " Not Found " ) ;
}
static void
dacp_reply_getproperty ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
2010-04-25 05:45:46 -04:00
struct player_status status ;
2010-01-29 16:39:27 -05:00
struct daap_session * s ;
2011-03-31 12:52:33 -04:00
const struct dacp_prop_map * dpm ;
2010-04-25 05:45:46 -04:00
struct media_file_info * mfi ;
struct evbuffer * proplist ;
2010-01-29 16:39:27 -05:00
const char * param ;
2011-03-31 12:52:33 -04:00
char * ptr ;
char * prop ;
char * propstr ;
2015-07-30 16:50:11 -04:00
size_t len ;
2010-04-25 05:45:46 -04:00
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
param = evhttp_find_header ( query , " properties " ) ;
if ( ! param )
{
DPRINTF ( E_WARN , L_DACP , " Invalid DACP getproperty request, no properties \n " ) ;
dmap_send_error ( req , " cmgt " , " Invalid request " ) ;
return ;
}
2011-03-31 12:52:33 -04:00
propstr = strdup ( param ) ;
if ( ! propstr )
2010-04-25 05:45:46 -04:00
{
2011-03-31 12:52:33 -04:00
DPRINTF ( E_LOG , L_DACP , " Could not duplicate properties parameter; out of memory \n " ) ;
2010-04-25 05:45:46 -04:00
2011-03-31 12:52:33 -04:00
dmap_send_error ( req , " cmgt " , " Out of memory " ) ;
2010-04-25 05:45:46 -04:00
return ;
}
proplist = evbuffer_new ( ) ;
if ( ! proplist )
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate evbuffer for properties list \n " ) ;
dmap_send_error ( req , " cmgt " , " Out of memory " ) ;
2011-03-31 12:52:33 -04:00
goto out_free_propstr ;
2010-04-25 05:45:46 -04:00
}
2010-01-29 16:39:27 -05:00
2010-04-25 05:45:46 -04:00
player_get_status ( & status ) ;
if ( status . status ! = PLAY_STOPPED )
2010-01-29 16:39:27 -05:00
{
2010-04-25 05:45:46 -04:00
mfi = db_file_fetch_byid ( status . id ) ;
if ( ! mfi )
{
DPRINTF ( E_LOG , L_DACP , " Could not fetch file id %d \n " , status . id ) ;
2010-01-29 16:39:27 -05:00
2010-04-25 05:45:46 -04:00
dmap_send_error ( req , " cmgt " , " Server error " ) ;
goto out_free_proplist ;
}
2010-01-29 16:39:27 -05:00
}
else
2010-04-25 05:45:46 -04:00
mfi = NULL ;
2011-03-31 12:52:33 -04:00
prop = strtok_r ( propstr , " , " , & ptr ) ;
while ( prop )
2010-04-25 05:45:46 -04:00
{
2011-03-31 12:52:33 -04:00
dpm = dacp_find_prop ( prop , strlen ( prop ) ) ;
2011-08-15 06:50:43 -04:00
if ( dpm )
2010-04-25 05:45:46 -04:00
{
2011-08-15 06:50:43 -04:00
if ( dpm - > propget )
dpm - > propget ( proplist , & status , mfi ) ;
else
DPRINTF ( E_WARN , L_DACP , " No getter method for DACP property %s \n " , prop ) ;
2010-04-25 05:45:46 -04:00
}
else
2011-08-15 06:50:43 -04:00
DPRINTF ( E_LOG , L_DACP , " Could not find requested property '%s' \n " , prop ) ;
2011-03-31 12:52:33 -04:00
prop = strtok_r ( NULL , " , " , & ptr ) ;
2010-04-25 05:45:46 -04:00
}
2011-03-31 12:52:33 -04:00
free ( propstr ) ;
2010-04-25 05:45:46 -04:00
if ( mfi )
free_mfi ( mfi , 0 ) ;
2015-07-30 16:50:11 -04:00
len = evbuffer_get_length ( proplist ) ;
dmap_add_container ( evbuf , " cmgt " , 12 + len ) ;
2010-04-25 05:45:46 -04:00
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12 */
ret = evbuffer_add_buffer ( evbuf , proplist ) ;
evbuffer_free ( proplist ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not add properties to getproperty reply \n " ) ;
dmap_send_error ( req , " cmgt " , " Out of memory " ) ;
return ;
}
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-04-25 05:45:46 -04:00
return ;
out_free_proplist :
evbuffer_free ( proplist ) ;
2011-03-31 12:52:33 -04:00
out_free_propstr :
free ( propstr ) ;
2010-01-29 16:39:27 -05:00
}
static void
dacp_reply_setproperty ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2011-03-31 12:52:33 -04:00
const struct dacp_prop_map * dpm ;
2010-04-25 06:24:46 -04:00
struct evkeyval * param ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
/* Known properties:
* dacp . shufflestate 0 / 1
* dacp . repeatstate 0 / 1 / 2
* dacp . playingtime seek to time in ms
* dmcp . volume 0 - 100 , float
*/
/* /ctrl-int/1/setproperty?dacp.shufflestate=1&session-id=100 */
2010-04-25 06:24:46 -04:00
TAILQ_FOREACH ( param , query , next )
{
2011-03-31 12:52:33 -04:00
dpm = dacp_find_prop ( param - > key , strlen ( param - > key ) ) ;
2010-04-25 06:24:46 -04:00
if ( ! dpm )
{
2010-11-28 10:01:01 -05:00
DPRINTF ( E_SPAM , L_DACP , " Unknown DACP property %s \n " , param - > key ) ;
2010-04-25 06:24:46 -04:00
continue ;
}
if ( dpm - > propset )
2010-05-05 12:58:13 -04:00
dpm - > propset ( param - > value , query ) ;
2010-04-25 06:24:46 -04:00
else
DPRINTF ( E_WARN , L_DACP , " No setter method for DACP property %s \n " , dpm - > desc ) ;
}
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
2010-04-04 14:26:12 -04:00
static void
2011-03-05 04:25:44 -05:00
speaker_enum_cb ( uint64_t id , const char * name , int relvol , struct spk_flags flags , void * arg )
2010-04-04 14:26:12 -04:00
{
struct evbuffer * evbuf ;
int len ;
evbuf = ( struct evbuffer * ) arg ;
2010-11-19 15:27:54 -05:00
len = 8 + strlen ( name ) + 28 ;
2011-03-05 04:25:44 -05:00
if ( flags . selected )
2010-04-04 14:26:12 -04:00
len + = 9 ;
2011-03-05 04:25:44 -05:00
if ( flags . has_password )
2010-04-04 14:26:12 -04:00
len + = 9 ;
2011-03-05 04:30:25 -05:00
if ( flags . has_video )
len + = 9 ;
2010-04-04 14:26:12 -04:00
dmap_add_container ( evbuf , " mdcl " , len ) ; /* 8 + len */
2011-03-05 04:25:44 -05:00
if ( flags . selected )
2010-04-04 14:26:12 -04:00
dmap_add_char ( evbuf , " caia " , 1 ) ; /* 9 */
2011-03-05 04:25:44 -05:00
if ( flags . has_password )
2010-04-04 14:26:12 -04:00
dmap_add_char ( evbuf , " cahp " , 1 ) ; /* 9 */
2011-03-05 04:30:25 -05:00
if ( flags . has_video )
dmap_add_char ( evbuf , " caiv " , 1 ) ; /* 9 */
2010-04-04 14:26:12 -04:00
dmap_add_string ( evbuf , " minm " , name ) ; /* 8 + len */
dmap_add_long ( evbuf , " msma " , id ) ; /* 16 */
2010-11-19 15:27:54 -05:00
dmap_add_int ( evbuf , " cmvo " , relvol ) ; /* 12 */
2010-04-04 14:26:12 -04:00
}
2010-01-29 16:39:27 -05:00
static void
dacp_reply_getspeakers ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2010-04-04 14:26:12 -04:00
struct evbuffer * spklist ;
2015-07-30 16:50:11 -04:00
size_t len ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-04 14:26:12 -04:00
spklist = evbuffer_new ( ) ;
if ( ! spklist )
{
DPRINTF ( E_LOG , L_DACP , " Could not create evbuffer for speaker list \n " ) ;
dmap_send_error ( req , " casp " , " Out of memory " ) ;
return ;
}
player_speaker_enumerate ( speaker_enum_cb , spklist ) ;
2015-07-30 16:50:11 -04:00
len = evbuffer_get_length ( spklist ) ;
dmap_add_container ( evbuf , " casp " , 12 + len ) ;
2010-04-04 14:26:12 -04:00
dmap_add_int ( evbuf , " mstt " , 200 ) ; /* 12 */
evbuffer_add_buffer ( evbuf , spklist ) ;
evbuffer_free ( spklist ) ;
2010-01-29 16:39:27 -05:00
2010-05-03 12:19:41 -04:00
httpd_send_reply ( req , HTTP_OK , " OK " , evbuf ) ;
2010-01-29 16:39:27 -05:00
}
static void
dacp_reply_setspeakers ( struct evhttp_request * req , struct evbuffer * evbuf , char * * uri , struct evkeyvalq * query )
{
struct daap_session * s ;
2010-04-04 15:21:32 -04:00
const char * param ;
const char * ptr ;
uint64_t * ids ;
int nspk ;
int i ;
int ret ;
2010-01-29 16:39:27 -05:00
s = daap_session_find ( req , query , evbuf ) ;
if ( ! s )
return ;
2010-04-04 15:21:32 -04:00
param = evhttp_find_header ( query , " speaker-id " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_DACP , " Missing speaker-id parameter in DACP setspeakers request \n " ) ;
2010-01-29 16:39:27 -05:00
2010-04-04 15:21:32 -04:00
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
if ( strlen ( param ) = = 0 )
{
ids = NULL ;
goto fastpath ;
}
nspk = 1 ;
ptr = param ;
while ( ( ptr = strchr ( ptr + 1 , ' , ' ) ) )
nspk + + ;
ids = ( uint64_t * ) malloc ( ( nspk + 1 ) * sizeof ( uint64_t ) ) ;
if ( ! ids )
{
DPRINTF ( E_LOG , L_DACP , " Out of memory for speaker ids \n " ) ;
evhttp_send_error ( req , HTTP_SERVUNAVAIL , " Internal Server Error " ) ;
return ;
}
param - - ;
i = 1 ;
do
{
param + + ;
2013-08-31 05:03:15 -04:00
2015-01-26 16:37:02 -05:00
ret = safe_hextou64 ( param , & ids [ i ] ) ;
2013-08-31 05:03:15 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Invalid speaker id in request: %s \n " , param ) ;
nspk - - ;
continue ;
}
else
2010-04-04 15:21:32 -04:00
{
2014-06-22 16:29:45 -04:00
DPRINTF ( E_DBG , L_DACP , " Speaker id converted with ret %d, param %s, dec val % " PRIu64 " . \n " , ret , param , ids [ i ] ) ;
2010-04-04 15:21:32 -04:00
}
i + + ;
}
while ( ( param = strchr ( param + 1 , ' , ' ) ) ) ;
ids [ 0 ] = nspk ;
fastpath :
ret = player_speaker_set ( ids ) ;
if ( ids )
free ( ids ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Speakers de/activation failed! \n " ) ;
/* Password problem */
if ( ret = = - 2 )
evhttp_send_error ( req , 902 , " " ) ;
else
evhttp_send_error ( req , 500 , " Internal Server Error " ) ;
return ;
}
2010-01-29 16:39:27 -05:00
/* 204 No Content is the canonical reply */
evhttp_send_reply ( req , HTTP_NOCONTENT , " No Content " , evbuf ) ;
}
static struct uri_map dacp_handlers [ ] =
{
{
. regexp = " ^/ctrl-int$ " ,
. handler = dacp_reply_ctrlint
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/cue$ " ,
. handler = dacp_reply_cue
} ,
2010-07-31 05:42:05 -04:00
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playspec$ " ,
. handler = dacp_reply_playspec
} ,
2010-01-29 16:39:27 -05:00
{
. regexp = " ^/ctrl-int/[[:digit:]]+/pause$ " ,
. handler = dacp_reply_pause
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playpause$ " ,
. handler = dacp_reply_playpause
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/nextitem$ " ,
. handler = dacp_reply_nextitem
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/previtem$ " ,
. handler = dacp_reply_previtem
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/beginff$ " ,
. handler = dacp_reply_beginff
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/beginrew$ " ,
. handler = dacp_reply_beginrew
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playresume$ " ,
. handler = dacp_reply_playresume
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playstatusupdate$ " ,
. handler = dacp_reply_playstatusupdate
} ,
2013-11-09 17:51:36 -05:00
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playqueue-contents$ " ,
. handler = dacp_reply_playqueuecontents
} ,
2013-11-14 17:14:58 -05:00
{
. regexp = " ^/ctrl-int/[[:digit:]]+/playqueue-edit$ " ,
. handler = dacp_reply_playqueueedit
} ,
2010-01-29 16:39:27 -05:00
{
. regexp = " ^/ctrl-int/[[:digit:]]+/nowplayingartwork$ " ,
. handler = dacp_reply_nowplayingartwork
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/getproperty$ " ,
. handler = dacp_reply_getproperty
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/setproperty$ " ,
. handler = dacp_reply_setproperty
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/getspeakers$ " ,
. handler = dacp_reply_getspeakers
} ,
{
. regexp = " ^/ctrl-int/[[:digit:]]+/setspeakers$ " ,
. handler = dacp_reply_setspeakers
} ,
{
. regexp = NULL ,
. handler = NULL
}
} ;
void
dacp_request ( struct evhttp_request * req )
{
char * full_uri ;
char * uri ;
char * ptr ;
char * uri_parts [ 7 ] ;
struct evbuffer * evbuf ;
struct evkeyvalq query ;
2014-05-29 17:22:00 -04:00
struct evkeyvalq * headers ;
2010-01-29 16:39:27 -05:00
int handler ;
int ret ;
int i ;
memset ( & query , 0 , sizeof ( struct evkeyvalq ) ) ;
full_uri = httpd_fixup_uri ( req ) ;
if ( ! full_uri )
{
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
ptr = strchr ( full_uri , ' ? ' ) ;
if ( ptr )
* ptr = ' \0 ' ;
uri = strdup ( full_uri ) ;
if ( ! uri )
{
2010-09-08 13:19:17 -04:00
free ( full_uri ) ;
2010-01-29 16:39:27 -05:00
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
return ;
}
if ( ptr )
* ptr = ' ? ' ;
ptr = uri ;
uri = evhttp_decode_uri ( uri ) ;
free ( ptr ) ;
DPRINTF ( E_DBG , L_DACP , " DACP request: %s \n " , full_uri ) ;
handler = - 1 ;
for ( i = 0 ; dacp_handlers [ i ] . handler ; i + + )
{
ret = regexec ( & dacp_handlers [ i ] . preg , uri , 0 , NULL , 0 ) ;
if ( ret = = 0 )
{
handler = i ;
break ;
}
}
if ( handler < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Unrecognized DACP request \n " ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
free ( uri ) ;
free ( full_uri ) ;
return ;
}
/* DACP has no HTTP authentication - Remote is identified by its pairing-guid */
memset ( uri_parts , 0 , sizeof ( uri_parts ) ) ;
uri_parts [ 0 ] = strtok_r ( uri , " / " , & ptr ) ;
for ( i = 1 ; ( i < sizeof ( uri_parts ) / sizeof ( uri_parts [ 0 ] ) ) & & uri_parts [ i - 1 ] ; i + + )
{
uri_parts [ i ] = strtok_r ( NULL , " / " , & ptr ) ;
}
if ( ! uri_parts [ 0 ] | | uri_parts [ i - 1 ] | | ( i < 2 ) )
{
DPRINTF ( E_LOG , L_DACP , " DACP URI has too many/few components (%d) \n " , ( uri_parts [ 0 ] ) ? i : 0 ) ;
evhttp_send_error ( req , HTTP_BADREQUEST , " Bad Request " ) ;
free ( uri ) ;
free ( full_uri ) ;
return ;
}
evbuf = evbuffer_new ( ) ;
if ( ! evbuf )
{
DPRINTF ( E_LOG , L_DACP , " Could not allocate evbuffer for DACP reply \n " ) ;
evhttp_send_error ( req , HTTP_SERVUNAVAIL , " Internal Server Error " ) ;
free ( uri ) ;
free ( full_uri ) ;
return ;
}
evhttp_parse_query ( full_uri , & query ) ;
2014-05-29 17:22:00 -04:00
headers = evhttp_request_get_output_headers ( req ) ;
evhttp_add_header ( headers , " DAAP-Server " , " forked-daapd/ " VERSION ) ;
2010-01-29 16:39:27 -05:00
/* Content-Type for all DACP replies; can be overriden as needed */
2014-05-29 17:22:00 -04:00
evhttp_add_header ( headers , " Content-Type " , " application/x-dmap-tagged " ) ;
2010-01-29 16:39:27 -05:00
dacp_handlers [ handler ] . handler ( req , evbuf , uri_parts , & query ) ;
evbuffer_free ( evbuf ) ;
evhttp_clear_headers ( & query ) ;
free ( uri ) ;
free ( full_uri ) ;
}
int
dacp_is_request ( struct evhttp_request * req , char * uri )
{
if ( strncmp ( uri , " /ctrl-int/ " , strlen ( " /ctrl-int/ " ) ) = = 0 )
return 1 ;
if ( strcmp ( uri , " /ctrl-int " ) = = 0 )
return 1 ;
return 0 ;
}
int
dacp_init ( void )
{
char buf [ 64 ] ;
int i ;
int ret ;
2010-04-24 06:06:15 -04:00
current_rev = 2 ;
2010-01-29 16:39:27 -05:00
update_requests = NULL ;
2010-04-24 07:00:15 -04:00
# ifdef USE_EVENTFD
update_efd = eventfd ( 0 , EFD_CLOEXEC ) ;
if ( update_efd < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not create update eventfd: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
# else
# if defined(__linux__)
ret = pipe2 ( update_pipe , O_CLOEXEC ) ;
# else
ret = pipe ( update_pipe ) ;
# endif
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_DACP , " Could not create update pipe: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
# endif /* USE_EVENTFD */
2010-01-29 16:39:27 -05:00
for ( i = 0 ; dacp_handlers [ i ] . handler ; i + + )
{
ret = regcomp ( & dacp_handlers [ i ] . preg , dacp_handlers [ i ] . regexp , REG_EXTENDED | REG_NOSUB ) ;
if ( ret ! = 0 )
{
regerror ( ret , & dacp_handlers [ i ] . preg , buf , sizeof ( buf ) ) ;
DPRINTF ( E_FATAL , L_DACP , " DACP init failed; regexp error: %s \n " , buf ) ;
2010-04-24 07:00:15 -04:00
goto regexp_fail ;
2010-01-29 16:39:27 -05:00
}
}
2010-04-24 07:00:15 -04:00
# ifdef USE_EVENTFD
2015-07-30 16:50:11 -04:00
updateev = event_new ( evbase_httpd , update_efd , EV_READ , playstatusupdate_cb , NULL ) ;
2010-04-24 07:00:15 -04:00
# else
2015-07-30 16:50:11 -04:00
updateev = event_new ( evbase_httpd , update_pipe [ 0 ] , EV_READ , playstatusupdate_cb , NULL ) ;
2010-04-24 07:00:15 -04:00
# endif
2015-07-30 16:50:11 -04:00
if ( ! updateev )
{
DPRINTF ( E_LOG , L_DACP , " Could not create update event \n " ) ;
return - 1 ;
}
event_add ( updateev , NULL ) ;
seek_timer = evtimer_new ( evbase_httpd , seek_timer_cb , NULL ) ;
if ( ! seek_timer )
{
DPRINTF ( E_LOG , L_DACP , " Could not create seek_timer event \n " ) ;
return - 1 ;
}
2010-04-24 07:00:15 -04:00
2015-05-18 14:12:18 -04:00
listener_add ( dacp_playstatus_update_handler , LISTENER_PLAYER ) ;
2010-04-24 07:00:15 -04:00
2010-01-29 16:39:27 -05:00
return 0 ;
2010-04-24 07:00:15 -04:00
regexp_fail :
# ifdef USE_EVENTFD
close ( update_efd ) ;
# else
close ( update_pipe [ 0 ] ) ;
close ( update_pipe [ 1 ] ) ;
# endif
return - 1 ;
2010-01-29 16:39:27 -05:00
}
void
dacp_deinit ( void )
{
struct dacp_update_request * ur ;
2014-05-29 17:22:00 -04:00
struct evhttp_connection * evcon ;
2010-01-29 16:39:27 -05:00
int i ;
2015-05-03 02:19:15 -04:00
listener_remove ( dacp_playstatus_update_handler ) ;
2010-09-12 08:31:41 -04:00
2015-07-30 16:50:11 -04:00
event_free ( seek_timer ) ;
2010-01-29 16:39:27 -05:00
for ( i = 0 ; dacp_handlers [ i ] . handler ; i + + )
regfree ( & dacp_handlers [ i ] . preg ) ;
for ( ur = update_requests ; update_requests ; ur = update_requests )
{
update_requests = ur - > next ;
2014-05-29 17:22:00 -04:00
evcon = evhttp_request_get_connection ( ur - > req ) ;
if ( evcon )
2010-07-30 15:42:53 -04:00
{
2014-05-29 17:22:00 -04:00
evhttp_connection_set_closecb ( evcon , NULL , NULL ) ;
evhttp_connection_free ( evcon ) ;
2010-07-30 15:42:53 -04:00
}
2010-07-23 12:15:18 -04:00
2010-01-29 16:39:27 -05:00
free ( ur ) ;
}
2010-04-24 07:00:15 -04:00
2015-07-30 16:50:11 -04:00
event_free ( updateev ) ;
2010-07-30 16:16:55 -04:00
2010-04-24 07:00:15 -04:00
# ifdef USE_EVENTFD
close ( update_efd ) ;
# else
close ( update_pipe [ 0 ] ) ;
close ( update_pipe [ 1 ] ) ;
# endif
2010-01-29 16:39:27 -05:00
}