Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ CFLAGS ?= -O2 -Wall

PREFIX ?= /usr/local

SRCS := shairport.c daemon.c rtsp.c mdns.c mdns_external.c mdns_tinysvcmdns.c common.c rtp.c metadata.c player.c alac.c audio.c audio_dummy.c audio_pipe.c tinysvcmdns.c
SRCS := shairport.c daemon.c rtsp.c mdns.c mdns_external.c mdns_tinysvcmdns.c common.c rtp.c metadata.c dacp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c tinysvcmdns.c
DEPS := config.mk alac.h audio.h common.h daemon.h getopt_long.h mdns.h metadata.h player.h rtp.h rtsp.h tinysvcmdns.h

ifdef CONFIG_SNDIO
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,44 @@ An example::
genre=Rock
comment=

DACP
----
Most devices allow the AirPlay receiver to have basic remote control over the stream. This is done via simple HTTP GET requests from the
receiver to the source device. The device sends a `DACP-ID` header to allow the receiver to find hostname and port numbers from mDNS, as well as an
`Active-Remote` header that must be used as an authentication key for issuing control commands. Similar to the Metadata feature outlined above,
this information can be read from a fifo after a device announces an audio stream. Using the `-D <directory name>` flag will cause a fifo named
`dacp` to be created in the directory specified. This fifo will have a similar format as the metadata.

Fields:
```
dacp_id=CFAE09D9DA7527E9
active_remote=3951361179
```

To issue control commands, you must do an mDNS lookup for `iTunes_Ctrl_<dacp_id>._dacp._tcp.local`. iTunes will always advertise on port 3689 so depending
on the environment, it may be possible to skip the mDNS lookup if you already know the source IP or hostname. iOS devices seem to choose a random high-numbered
port. Once you have found the host and port number, you may issue HTTP GET requests in the following format:

`http://<hostname>:<port>/ctrl-int/1/<cmd>`

Where `<cmd>` may be one of:
```
beginff
beginrew
mutetoggle
nextitem
previtem
pause
playpause
play
stop
playresume (after issuing beginff or beginrew)
shuffle_songs
volumedown
volumeup
```
Your HTTP request must include an `Active-Remote` header with the value obtained from the fifo.


Thanks
------
Expand Down
1 change: 1 addition & 0 deletions common.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ typedef struct {
char *cmd_start, *cmd_stop;
int cmd_blocking;
char *meta_dir;
char *dacp_dir;
char *pidfile;
char *logfile;
char *errfile;
Expand Down
111 changes: 111 additions & 0 deletions dacp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* DACP remote methods. This file is part of Shairport.
* Copyright (c) 2014 Josh Butts <josh@joshbutts.com>
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

#include <memory.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/md5.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "common.h"
#include "dacp.h"

dacpdata player_dacp;
static int fd = -1;
static int dirty = 0;

void dacp_set(char** field, const char* value) {
if (*field) {
if (!strcmp(*field, value))
return;
free(*field);
}
*field = strdup(value);
dirty = 1;
}

void dacp_open(void) {
if (!config.dacp_dir)
return;

const char fn[] = "dacp";
size_t pl = strlen(config.dacp_dir) + 1 + strlen(fn);

char* path = malloc(pl+1);
snprintf(path, pl+1, "%s/%s", config.dacp_dir, fn);

if (mkfifo(path, 0644) && errno != EEXIST)
die("Could not create dacp FIFO %s", path);

fd = open(path, O_WRONLY | O_NONBLOCK);
if (fd < 0)
debug(1, "Could not open dacp FIFO %s. Will try again later.", path);

free(path);
}

static void dacp_close(void) {
close(fd);
fd = -1;
}

static void print_one(const char *name, const char *value) {
write_unchecked(fd, name, strlen(name));
write_unchecked(fd, "=", 1);
if (value)
write_unchecked(fd, value, strlen(value));
write_unchecked(fd, "\n", 1);
}

#define write_one(name) \
print_one(#name, player_dacp.name)

void dacp_write(void) {
int ret;

// readers may go away and come back
if (fd < 0)
dacp_open();
if (fd < 0)
return;

if (!dirty)
return;

dirty = 0;

write_one(dacp_id);
write_one(active_remote);

ret = write(fd, "\n", 1);
if (ret < 1) // no reader
dacp_close();
}
17 changes: 17 additions & 0 deletions dacp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef _DACP_H
#define _DACP_H

#include <stdio.h>

typedef struct {
char *dacp_id;
char *active_remote;
} dacpdata;

void dacp_set(char** field, const char* value);
void dacp_open(void);
void dacp_write(void);

extern dacpdata player_dacp;

#endif // _DACP_H
11 changes: 11 additions & 0 deletions rtsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "rtp.h"
#include "mdns.h"
#include "metadata.h"
#include "dacp.h"

#ifdef AF_INET6
#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN
Expand Down Expand Up @@ -604,6 +605,16 @@ static void handle_announce(rtsp_conn_info *conn,
for (i=0; i<sizeof(conn->stream.fmtp)/sizeof(conn->stream.fmtp[0]); i++)
conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));

int c;
for (c = 0; c < req->nheaders; c++) {
if (!strcmp(req->name[c], "DACP-ID")) {
dacp_set(&player_dacp.dacp_id, req->value[c]);
} else if (!strcmp(req->name[c], "Active-Remote")) {
dacp_set(&player_dacp.active_remote, req->value[c]);
}
}
dacp_write();

resp->respcode = 200;
}

Expand Down
11 changes: 10 additions & 1 deletion shairport.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "mdns.h"
#include "getopt_long.h"
#include "metadata.h"
#include "dacp.h"

static const char *version =
#include "version.h"
Expand Down Expand Up @@ -107,6 +108,7 @@ void usage(char *progname) {
printf(" -E, --on-stop=COMMAND run a shell command when playback ends\n");
printf(" -w, --wait-cmd block while the shell command(s) run\n");
printf(" -M, --meta-dir=DIR set a directory to write metadata and album cover art to\n");
printf(" -D, --dacp-dir=DIR set a directory to write DACP remote control info to\n");

printf(" -o, --output=BACKEND select audio output method\n");
printf(" -m, --mdns=BACKEND force the use of BACKEND to advertise the service\n");
Expand Down Expand Up @@ -138,12 +140,13 @@ int parse_options(int argc, char **argv) {
{"wait-cmd", no_argument, NULL, 'w'},
{"meta-dir", required_argument, NULL, 'M'},
{"mdns", required_argument, NULL, 'm'},
{"dacp-dir", required_argument, NULL, 'D'},
{NULL, 0, NULL, 0}
};

int opt;
while ((opt = getopt_long(argc, argv,
"+hdvP:l:e:p:a:k:o:b:B:E:M:wm:",
"+hdvP:l:e:p:a:k:o:b:B:E:M:D:wm:",
long_options, NULL)) > 0) {
switch (opt) {
default:
Expand Down Expand Up @@ -197,6 +200,9 @@ int parse_options(int argc, char **argv) {
case 'm':
config.mdns_name = optarg;
break;
case 'D':
config.dacp_dir = optarg;
break;
}
}
return optind;
Expand Down Expand Up @@ -315,6 +321,9 @@ int main(int argc, char **argv) {
if (config.meta_dir)
metadata_open();

if (config.dacp_dir)
dacp_open();

rtsp_listen_loop();

// should not.
Expand Down