./disorder-playrtp --help > /dev/null
# check that the command completions are up to date
-check-completions:
+check-completions: disorder
./disorder --help-commands \
| awk '/^ [a-z]/ { print $$1 }' \
| sort > ,commands
{ "max", required_argument, 0, 'x' },
{ "buffer", required_argument, 0, 'b' },
{ "rcvbuf", required_argument, 0, 'R' },
- { "multicast", required_argument, 0, 'M' },
#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
{ "oss", no_argument, 0, 'o' },
#endif
" --buffer, -b FRAMES Buffer high water mark\n"
" --max, -x FRAMES Buffer maximum size\n"
" --rcvbuf, -R BYTES Socket receive buffer size\n"
- " --multicast, -M GROUP Join multicast group\n"
" --config, -C PATH Set configuration file\n"
#if HAVE_ALSA_ASOUNDLIB_H
" --alsa, -a Use ALSA to play audio\n"
char *sockname;
int rcvbuf, target_rcvbuf = 131072;
socklen_t len;
- char *multicast_group = 0;
struct ip_mreq mreq;
struct ipv6_mreq mreq6;
disorder_client *c;
char *address, *port;
+ int is_multicast;
+ union any_sockaddr {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ };
+ union any_sockaddr mgroup;
static const struct addrinfo prefs = {
AI_PASSIVE,
case 'x': maxbuffer = 2 * atol(optarg); break;
case 'L': logfp = fopen(optarg, "w"); break;
case 'R': target_rcvbuf = atoi(optarg); break;
- case 'M': multicast_group = optarg; break;
#if HAVE_ALSA_ASOUNDLIB_H
case 'a': backend = playrtp_alsa; break;
#endif
argv += optind;
switch(argc) {
case 0:
- case 1:
+ /* Get configuration from server */
if(!(c = disorder_new(1))) exit(EXIT_FAILURE);
if(disorder_connect(c)) exit(EXIT_FAILURE);
if(disorder_rtp_address(c, &address, &port)) exit(EXIT_FAILURE);
- sl.n = 1;
- sl.s = &port;
- /* set multicast_group if address is a multicast address */
+ sl.n = 2;
+ sl.s = xcalloc(2, sizeof *sl.s);
+ sl.s[0] = address;
+ sl.s[1] = port;
break;
+ case 1:
case 2:
+ /* Use command-line ADDRESS+PORT or just PORT */
sl.n = argc;
sl.s = argv;
break;
default:
- fatal(0, "usage: disorder-playrtp [OPTIONS] [ADDRESS [PORT]]");
+ fatal(0, "usage: disorder-playrtp [OPTIONS] [[ADDRESS] PORT]");
}
- /* Listen for inbound audio data */
+ /* Look up address and port */
if(!(res = get_address(&sl, &prefs, &sockname)))
exit(1);
- info("listening on %s", sockname);
+ /* Create the socket */
if((rtpfd = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol)) < 0)
fatal(errno, "error creating socket");
+ /* Stash the multicast group address */
+ if((is_multicast = multicast(res->ai_addr))) {
+ memcpy(&mgroup, res->ai_addr, res->ai_addrlen);
+ switch(res->ai_addr->sa_family) {
+ case AF_INET:
+ mgroup.in.sin_port = 0;
+ break;
+ case AF_INET6:
+ mgroup.in6.sin6_port = 0;
+ break;
+ }
+ }
+ /* Bind to 0/port */
+ switch(res->ai_addr->sa_family) {
+ case AF_INET:
+ memset(&((struct sockaddr_in *)res->ai_addr)->sin_addr, 0,
+ sizeof (struct in_addr));
+ break;
+ case AF_INET6:
+ memset(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 0,
+ sizeof (struct in6_addr));
+ break;
+ default:
+ fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family);
+ }
if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
fatal(errno, "error binding socket to %s", sockname);
- if(multicast_group) {
- if((n = getaddrinfo(multicast_group, 0, &prefs, &res)))
- fatal(0, "getaddrinfo %s: %s", multicast_group, gai_strerror(n));
- switch(res->ai_family) {
+ if(is_multicast) {
+ switch(mgroup.sa.sa_family) {
case PF_INET:
- mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
+ mreq.imr_multiaddr = mgroup.in.sin_addr;
mreq.imr_interface.s_addr = 0; /* use primary interface */
if(setsockopt(rtpfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof mreq) < 0)
fatal(errno, "error calling setsockopt IP_ADD_MEMBERSHIP");
break;
case PF_INET6:
- mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+ mreq6.ipv6mr_multiaddr = mgroup.in6.sin6_addr;
memset(&mreq6.ipv6mr_interface, 0, sizeof mreq6.ipv6mr_interface);
if(setsockopt(rtpfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&mreq6, sizeof mreq6) < 0)
default:
fatal(0, "unsupported address family %d", res->ai_family);
}
- }
+ info("listening on %s multicast group %s",
+ format_sockaddr(res->ai_addr), format_sockaddr(&mgroup.sa));
+ } else
+ info("listening on %s", format_sockaddr(res->ai_addr));
len = sizeof rcvbuf;
if(getsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0)
fatal(errno, "error calling getsockopt SO_RCVBUF");
+disorder (1.5.99+dev8) unstable; urgency=low
+
+ * disorder-playrtp automatically picks up multicast
+ * Disobedience no longer messes with random play settings on
+ start/recovery
+
+ -- Richard Kettlewell <rjk@greenend.org.uk> Thu, 22 Nov 2007 17:17:25 +0000
+
disorder (1.5.99+dev7) unstable; urgency=low
* New Disobedience now it's a bit quicker
.B disorder-playrtp
.RI [ OPTIONS ]
.RB [ -- ]
-.I ADDRESS
-.I PORT
+.RI [[ GROUP ]
+.IR PORT ]
.SH DESCRIPTION
\fBdisorder-playrtp\fR plays a network broadcast sent from the specified
address.
.PP
-Normally the \fIADDRESS\fR would be 0.0.0.0. The \fIPORT\fR will depend on the
-server configuration. You may need to specify the \fB--multicast\fR option
-(see below) and if you have more than one soundcard perhaps the \fB--device\fR
-option.
+If neither a group nor port are specified then the local DisOrder
+configuration is consulted to find the server and the server is asked where the
+RTP stream is.
+.PP
+If just a port is specified then the RTP stream is assumed to be unicast or
+broadcast to that port.
+.PP
+If a group and a port are specified then the RTP stream is assumed to be
+multicast to that group and port.
.SH OPTIONS
.TP
.B --device \fIDEVICE\fR, \fB-D \fIDEVICE\fR
Specifies the audio device to use. The exact meaning of this is
platform-dependent; on Linux it is the ALSA device name.
.TP
-.B --multicast \fIGROUP\fR, \fB-M \fIGROUP\fR
-Specifies a multicast group to join. This is necessary if the network
-broadcast is being multicasted rather than broadcast.
-.TP
.B --help\fR, \fB-h
Display a usage message.
.TP
/*
* This file is part of DisOrder.
- * Copyright (C) 2004 Richard Kettlewell
+ * Copyright (C) 2004, 2007 Richard Kettlewell
*
* 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
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
#include "log.h"
#include "printf.h"
#include "configuration.h"
#include "addr.h"
+#include "mem.h"
/** @brief Convert a pair of strings to an address
* @param a Pointer to string list
return memcmp(a->ai_addr, b->ai_addr, a->ai_addrlen); /* kludge */
}
}
-
+
+static inline int multicast4(const struct sockaddr_in *sin) {
+ return IN_MULTICAST(ntohl(sin->sin_addr.s_addr));
+}
+
+static inline int multicast6(const struct sockaddr_in6 *sin6) {
+ return IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr);
+}
+
+/** @brief Return true if @p sa represents a multicast address */
+int multicast(const struct sockaddr *sa) {
+ switch(sa->sa_family) {
+ case AF_INET:
+ return multicast4((const struct sockaddr_in *)sa);
+ case AF_INET6:
+ return multicast6((const struct sockaddr_in6 *)sa);
+ default:
+ return 0;
+ }
+}
+
+static inline char *format_sockaddr4(const struct sockaddr_in *sin) {
+ char buffer[1024], *r;
+
+ if(sin->sin_port)
+ byte_xasprintf(&r, "%s port %u",
+ inet_ntop(sin->sin_family, &sin->sin_addr,
+ buffer, sizeof buffer),
+ ntohs(sin->sin_port));
+ else
+ byte_xasprintf(&r, "%s",
+ inet_ntop(sin->sin_family, &sin->sin_addr,
+ buffer, sizeof buffer));
+ return r;
+}
+
+static inline char *format_sockaddr6(const struct sockaddr_in6 *sin6) {
+ char buffer[1024], *r;
+
+ if(sin6->sin6_port)
+ byte_xasprintf(&r, "%s port %u",
+ inet_ntop(sin6->sin6_family, &sin6->sin6_addr,
+ buffer, sizeof buffer),
+ ntohs(sin6->sin6_port));
+ else
+ byte_xasprintf(&r, "%s",
+ inet_ntop(sin6->sin6_family, &sin6->sin6_addr,
+ buffer, sizeof buffer));
+ return r;
+}
+
+static inline char *format_sockaddrun(const struct sockaddr_un *sun) {
+ return xstrdup(sun->sun_path);
+}
+
+/** @brief Construct a text description a sockaddr */
+char *format_sockaddr(const struct sockaddr *sa) {
+ switch(sa->sa_family) {
+ case AF_INET:
+ return format_sockaddr4((const struct sockaddr_in *)sa);
+ case AF_INET6:
+ return format_sockaddr6((const struct sockaddr_in6 *)sa);
+ case AF_UNIX:
+ return format_sockaddrun((const struct sockaddr_un *)sa);
+ default:
+ return 0;
+ }
+}
+
/*
Local Variables:
c-basic-offset:2
int addrinfocmp(const struct addrinfo *a,
const struct addrinfo *b);
+int multicast(const struct sockaddr *sa);
+char *format_sockaddr(const struct sockaddr *sa);
+
#endif /* ADDR_H */
/*
return 0;
}
-static void post_move_cleanup(void) {
- struct queue_entry *q;
-
- /* If we have caused any random tracks to not be at the end then we make them
- * no longer be random. */
- for(q = qhead.next; q != &qhead; q = q->next)
- if(q->state == playing_random && q->next != &qhead)
- q->state = playing_unplayed;
- /* That might mean we need to add a new random track. */
- add_random_track();
- queue_write();
-}
-
static int c_move(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
return 1;
}
n = queue_move(q, atoi(vec[1]), c->who);
- post_move_cleanup();
sink_printf(ev_writer_sink(c->w), "252 %d\n", n);
/* If we've moved to the head of the queue then prepare the track. */
if(q == qhead.next)
return 1;
}
queue_moveafter(q, nvec, qs, c->who);
- post_move_cleanup();
sink_printf(ev_writer_sink(c->w), "250 Moved tracks\n");
/* If we've moved to the head of the queue then prepare the track. */
if(q == qhead.next)
res->ai_socktype,
res->ai_protocol)) < 0)
fatal(errno, "error creating broadcast socket");
- if((res->ai_family == PF_INET
- && IN_MULTICAST(
- ntohl(((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr)
- ))
- || (res->ai_family == PF_INET6
- && IN6_IS_ADDR_MULTICAST(
- &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr
- ))) {
+ if(multicast(res->ai_addr)) {
/* Multicasting */
switch(res->ai_family) {
case PF_INET: {