Merge branch 'development'

Update Docker images o that additional command line arguments to the docker run command are passed to the shairport-sync application.
(This is the behaviour of the original 3.3.8 docker image but was missing from the AirPlay 2 development version.)
Use the s6-overlay system on the Classic docker image.
Fix a bug that delayed metadata appearing in the D-Bus, MPRIS and MQTT interfaces.
Clean up the generation of Play/Pause/Stop conditions.
Add Pause stage generation for AirPlay Buffered streams.
CLean up some of the documents.
This commit is contained in:
Mike Brady
2022-10-24 09:52:34 +01:00
33 changed files with 136 additions and 127 deletions
+5 -11
View File
@@ -16,21 +16,18 @@ RUN apk -U add \
dbus \
alsa-lib-dev \
popt-dev \
mbedtls-dev \
soxr-dev \
avahi-dev \
libconfig-dev \
libsndfile-dev \
mosquitto-dev \
xmltoman \
openssh-client \
libsodium-dev \
libgcrypt-dev \
ffmpeg-dev \
xxd \
libressl-dev \
openssl-dev \
libplist-dev \
libgcrypt-dev
libplist-dev
##### ALAC #####
RUN git clone https://github.com/mikebrady/alac
@@ -76,7 +73,6 @@ RUN apk -U add \
dbus \
popt \
glib \
mbedtls \
soxr \
avahi \
avahi-tools \
@@ -86,8 +82,8 @@ RUN apk -U add \
libuuid \
ffmpeg \
libsodium \
libplist \
libgcrypt
libgcrypt \
libplist
# Copy build files.
COPY --from=builder /shairport-sync/build/install/usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
@@ -111,6 +107,4 @@ RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgr
# Remove anything we don't need.
RUN rm -rf /lib/apk/db/*
ENTRYPOINT [ "/init" ]
CMD [ "/usr/local/bin/shairport-sync", "-v", "--statistics" ]
ENTRYPOINT [ "/init", "s6-setuidgid", "shairport-sync", "/usr/local/bin/shairport-sync" ]
+20 -26
View File
@@ -2,43 +2,37 @@
Available at: https://hub.docker.com/r/mikebrady/shairport-sync
The following docker tags are available:
```
[tag]
[tag]-classic
Versions with tags including `-classic` provide classic AirPlay, the same as Shairport Sync versions up to 3.3.9.
(build from newest tag)
stable
stable-classic
(latest build from master)
latest
latest-classic
(latest build from development)
development
development-classic
```
(**Note:** At this time, `stable` tags may not be available.)
## Example Docker Compose File
See the `docker-compose.yaml` file in this folder for an example.
## Example Docker Run
## Docker Run
To run the latest stable release of Shairport Sync, which provides AirPlay 2 service:
```
$ docker run -d --restart unless-stopped --net host --device /dev/snd \
mikebrady/shairport-sync:<tag>
mikebrady/shairport-sync:stable
```
To run the classic version:
```
$ docker run -d --restart unless-stopped --net host --device /dev/snd \
mikebrady/shairport-sync:stable-classic
```
### Options
You can change the default commands passed to Shairport Sync. Here is an example:
Command line options will be passed to Shairport Sync. Here is an example:
```
$ docker run -d --restart unless-stopped --net host --device /dev/snd \
mikebrady/shairport-sync:<tag> shairport-sync -v \
--statistics -a DenSystem -d hw:0 -c PCM
mikebrady/shairport-sync:stable \
-v --statistics -a DenSystem -d hw:0 -c PCM
```
This will sent audio to alsa hardware device `hw:0` and make use of the that device's mixer control called `PCM`. The service will be visible as `DenSystem` on the network.
This will send audio to alsa hardware device `hw:0` and make use of the that device's mixer control called `PCM`. The service will be visible as `DenSystem` on the network.
## Configuration File
@@ -48,15 +42,15 @@ To get access to the full range of configuration options, pass the configuration
### Build Example (for arm7 devices)
```
docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development --build-arg NQPTP_BRANCH=development --no-cache -t shairport-sync:unstable-development .
docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development --build-arg NQPTP_BRANCH=development --no-cache -t shairport-sync:development .
```
`SHAIRPORT_SYNC_BRANCH` and `NQPTP_BRANCH` are required to ensure the image is built using the expected branch.
`--no-cache` needs to be used to force buildx to pull the NQPTP branch for new updates. This slows down the build time though so can be removed when it is not beneficial during testing.
### AirPlay 1 Only
### "Classic" AirPlay
The AirPlay 1 only dockerfile is in the `classic` folder. This also includes the `start.sh` script used by the container. Please note that the AirPlay 1 image built via the AirPlay 2 branch has missing functionality, e.g. it does not work with multiple instances on the same hardware; does not support iTunes for Windows etc.
The "Classic" AirPlay only dockerfile is in the `classic` folder. This also includes the `start.sh` script used by the container.
### GitHub Action Builds
@@ -64,4 +58,4 @@ Requires the following secrets to be set in the repo:
- `DOCKER_REGISTRY` - docker.io if using Docker Hub, else set to your registry URL.
- `DOCKER_REGISTRY_TOKEN` - Access token for your registry.
- `DOCKER_REGISTRY_USER` - Login user for your registry.
- `DOCKER_IMAGE_NAME` - The name of the image, for example `your-registry.com/shairport-sync` or just `your-username/shairport-sync` if using Docker Hub.
- `DOCKER_IMAGE_NAME` - The name of the image, for example `your-registry.com/shairport-sync` or just `your-username/shairport-sync` if using Docker Hub.
+42 -57
View File
@@ -1,5 +1,12 @@
FROM alpine AS builder-base
# General Build System:
FROM alpine:3.12 AS builder
# Classic (aka AirPlay 1) Build
# Check required arguments exist. These will be provided by the Github Action
# Workflow and are required to ensure the correct branches are being used.
ARG SHAIRPORT_SYNC_BRANCH
RUN test -n "$SHAIRPORT_SYNC_BRANCH"
RUN apk -U add \
git \
build-base \
@@ -7,63 +14,42 @@ RUN apk -U add \
automake \
libtool \
dbus \
su-exec \
alsa-lib-dev \
libdaemon-dev \
popt-dev \
mbedtls-dev \
soxr-dev \
avahi-dev \
libconfig-dev \
libsndfile-dev \
mosquitto-dev \
xmltoman
# ALAC Build System:
FROM builder-base AS builder-alac
libsndfile-dev
##### ALAC #####
RUN git clone https://github.com/mikebrady/alac
WORKDIR alac
RUN autoreconf -fi
WORKDIR /alac
RUN autoreconf -i
RUN ./configure
RUN make
RUN make install
WORKDIR /
##### ALAC END #####
# Shairport Sync Build System:
FROM builder-base AS builder-sps
# This will be modified by the Github Action Workflow and is required
# to ensure the correct branch is being used.
ARG SHAIRPORT_SYNC_BRANCH
RUN test -n "$SHAIRPORT_SYNC_BRANCH"
COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
COPY --from=builder-alac /usr/local/include /usr/local/include
##### SPS #####
WORKDIR /shairport-sync
COPY . .
RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
RUN autoreconf -fi
RUN ./configure \
--with-alsa \
--with-dummy \
--with-pipe \
--with-stdout \
--with-avahi \
--with-ssl=mbedtls \
--with-soxr \
--sysconfdir=/etc \
--with-dbus-interface \
--with-mpris-interface \
--with-mqtt-client \
--with-apple-alac \
--with-convolution
WORKDIR /shairport-sync/build
RUN autoreconf -i ../
RUN ../configure --sysconfdir=/etc --with-alsa --with-soxr --with-avahi --with-ssl=mbedtls \
--with-metadata --with-dummy --with-pipe --with-dbus-interface \
--with-stdout --with-mpris-interface --with-mqtt-client \
--with-apple-alac --with-convolution
RUN make -j $(nproc)
RUN make install
RUN DESTDIR=install make install
WORKDIR /
##### SPS END #####
# Shairport Sync Runtime System:
FROM alpine
# Shairport Sync Runtime System
FROM crazymax/alpine-s6:3.12-3.1.1.2
RUN apk -U add \
alsa-lib \
@@ -75,30 +61,29 @@ RUN apk -U add \
avahi \
avahi-tools \
libconfig \
libsndfile \
mosquitto-libs \
su-exec \
libgcc \
libgc++
mosquitto \
libsndfile-dev
RUN rm -rf /lib/apk/db/*
COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder-sps /etc/shairport-sync* /etc/
COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
# Copy build files.
COPY --from=builder /shairport-sync/build/install/usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
COPY --from=builder /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder /shairport-sync/build/install/etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
COPY --from=builder /shairport-sync/build/install/etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
COPY ./docker/classic/etc/s6-overlay/s6-rc.d /etc/s6-overlay/s6-rc.d
RUN chmod +x /etc/s6-overlay/s6-rc.d/startup/script.sh
# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
RUN addgroup shairport-sync
RUN addgroup shairport-sync
RUN adduser -D shairport-sync -G shairport-sync
# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio
RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgroup shairport-sync audio
COPY ./docker/classic/start.sh /
RUN chmod +x /start.sh
# Remove anything we don't need.
RUN rm -rf /lib/apk/db/*
ENTRYPOINT [ "/start.sh" ]
ENTRYPOINT [ "/init", "s6-setuidgid", "shairport-sync", "/usr/local/bin/shairport-sync" ]
+1 -1
View File
@@ -1,3 +1,3 @@
# Classic (AirPlay 1 Only) Docker Image
# Classic (aka AirPlay 1 Only) Docker Image
See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags.
@@ -0,0 +1,2 @@
#!/command/execlineb -S0
/run/s6/basedir/bin/halt
@@ -0,0 +1,4 @@
#!/command/with-contenv sh
rm -rf /var/run/dbus.pid
echo "Starting dbus"
exec dbus-daemon --system --nofork
@@ -0,0 +1 @@
longrun
@@ -0,0 +1,2 @@
#!/command/execlineb -S0
/run/s6/basedir/bin/halt
@@ -0,0 +1,8 @@
#!/command/with-contenv sh
sleep 5
while [ ! -f /var/run/dbus.pid ]; do
"dbus is not running, sleeping for 5 seconds before trying to start avahi"
sleep 5
done
echo "Starting avahi"
exec avahi-daemon --no-chroot
@@ -0,0 +1 @@
longrun
@@ -0,0 +1,2 @@
#!/bin/sh
echo "STARTING - $(date)"
@@ -0,0 +1 @@
oneshot
@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/startup/script.sh
+1 -1
View File
@@ -10,4 +10,4 @@ dbus-daemon --system
avahi-daemon --daemonize --no-chroot
su-exec shairport-sync shairport-sync "$@"
su-exec shairport-sync shairport-sync "$@"
+2 -2
View File
@@ -2,7 +2,7 @@
services:
shairport-sync:
image: mikebrady/shairport-sync:<tag>
image: mikebrady/shairport-sync:stable
network_mode: host
restart: unless-stopped
devices:
@@ -12,4 +12,4 @@ services:
logging:
options:
max-size: "200k"
max-file: "10"
max-file: "10"
+1 -1
View File
@@ -1,2 +1,2 @@
#!/command/execlineb -S0
/run/s6/basedir/bin/halt
/run/s6/basedir/bin/halt
+1 -1
View File
@@ -1,4 +1,4 @@
#!/command/with-contenv sh
rm -rf /var/run/dbus.pid
echo "Starting dbus"
exec dbus-daemon --system --nofork
exec dbus-daemon --system --nofork
+1 -1
View File
@@ -1 +1 @@
longrun
longrun
@@ -1,2 +1,2 @@
#!/command/execlineb -S0
/run/s6/basedir/bin/halt
/run/s6/basedir/bin/halt
+1 -1
View File
@@ -5,4 +5,4 @@ while [ ! -f /var/run/dbus.pid ]; do
sleep 5
done
echo "Starting avahi"
exec avahi-daemon --no-chroot
exec avahi-daemon --no-chroot
+1 -1
View File
@@ -1 +1 @@
longrun
longrun
@@ -1,2 +1,2 @@
#!/command/execlineb -S0
/run/s6/basedir/bin/halt
/run/s6/basedir/bin/halt
+1 -1
View File
@@ -1,3 +1,3 @@
#!/command/with-contenv sh
echo "Starting nqptp"
exec /usr/local/bin/nqptp -v
exec /usr/local/bin/nqptp
+1 -1
View File
@@ -1 +1 @@
longrun
longrun
@@ -1,2 +1,2 @@
#!/bin/sh
echo "STARTING - $(date)"
echo "STARTING - $(date)"
+1 -1
View File
@@ -1 +1 @@
oneshot
oneshot
+1 -1
View File
@@ -1 +1 @@
/etc/s6-overlay/s6-rc.d/startup/script.sh
/etc/s6-overlay/s6-rc.d/startup/script.sh
+18 -8
View File
@@ -5,7 +5,7 @@
* then you need a metadata hub,
* where everything is stored
* This file is part of Shairport Sync.
* Copyright (c) Mike Brady 2017--2020
* Copyright (c) Mike Brady 2017--2022
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -326,18 +326,21 @@ char *metadata_write_image_file(const char *buf, int len) {
return path;
}
int metadata_packet_item_changed = 0; // set if any parsed part of a metadata stream changes
void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length) {
// metadata coming in from the audio source or from Shairport Sync itself passes through here
// this has more information about tags, which might be relevant:
// https://code.google.com/p/ytrack/wiki/DMAP
// all the following items of metadata are contained in one metadata packet
// they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
// we don't set "changed" for them individually; instead we set it when the 'mden' token
// comes in if the metadata_packet_item_changed is set.
// Some metadata items are contained in one metadata packet.
// The start of the metadata packet is signalled by an 'ssnc' 'mdst' item and
// the end of it by an 'ssnc 'mden' item.
// We don't set "changed" for them individually; instead we set it when the 'mden' token
// comes in if the metadata_packet_item_changed item is set by parsed items
// within the packet.
int changed = 0;
int metadata_packet_item_changed = 0;
metadata_hub_modify_prolog();
pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
@@ -507,7 +510,10 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_packet_item_changed = 0;
break;
case 'mden':
debug(2, "MH Metadata stream processing end.");
if (metadata_packet_item_changed != 0)
debug(2, "MH Metadata stream processing end with changes.");
else
debug(2, "MH Metadata stream processing end without changes.");
changed = metadata_packet_item_changed;
break;
case 'PICT':
@@ -568,6 +574,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
changed = (metadata_store.active_state != AM_INACTIVE);
metadata_store.active_state = AM_INACTIVE;
break;
case 'pres':
case 'pbeg':
changed = ((metadata_store.player_state != PS_PLAYING) ||
(metadata_store.player_thread_active == 0));
@@ -580,15 +587,18 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_store.player_state = PS_STOPPED;
metadata_store.player_thread_active = 0;
break;
case 'pfls':
case 'paus':
changed = (metadata_store.player_state != PS_PAUSED);
metadata_store.player_state = PS_PAUSED;
break;
/*
// not using this anymore.
case 'pffr': // this is sent when the first frame has been received
case 'prsm':
changed = (metadata_store.player_state != PS_PLAYING);
metadata_store.player_state = PS_PLAYING;
break;
*/
case 'pvol': {
// Note: it's assumed that the config.airplay volume has already been correctly set.
// int32_t actual_volume;
-5
View File
@@ -1157,7 +1157,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
conn->connection_number, conn->first_packet_timestamp, lt * 0.000000001);
#ifdef CONFIG_METADATA
// say we have started receiving frames here
debug(2, "pffr");
send_ssnc_metadata(
'pffr', NULL, 0,
0); // "first frame received", but don't wait if the queue is locked
@@ -1303,7 +1302,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
}
#ifdef CONFIG_METADATA
if (conn->ab_buffering == 0) {
debug(2, "prsm");
send_ssnc_metadata('prsm', NULL, 0,
0); // "resume", but don't wait if the queue is locked
}
@@ -3388,7 +3386,6 @@ void player_flush(uint32_t timestamp, rtsp_conn_info *conn) {
// only send a flush metadata message if the first packet has been seen -- it's a bogus message
// otherwise
if (conn->first_packet_timestamp) {
debug(2, "pfls");
char numbuf[32];
snprintf(numbuf, sizeof(numbuf), "%u", timestamp);
send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points
@@ -3458,7 +3455,6 @@ int player_play(rtsp_conn_info *conn) {
debug(1, "Error creating player_thread: %s", strerror(errno));
#ifdef CONFIG_METADATA
debug(2, "pbeg");
send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points
#endif
return 0;
@@ -3484,7 +3480,6 @@ int player_stop(rtsp_conn_info *conn) {
free(conn->player_thread);
conn->player_thread = NULL;
#ifdef CONFIG_METADATA
debug(2, "pend");
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
#endif
// debuglev = dl;
+13 -4
View File
@@ -2001,13 +2001,20 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
debug(2, "Connection %d: Start playing, with anchor clock %" PRIx64 ".",
conn->connection_number, conn->networkTimeTimelineID);
activity_monitor_signify_activity(1);
#ifdef CONFIG_METADATA
send_ssnc_metadata('pres', NULL, 0, 1); // resume -- contains cancellation points
#endif
conn->ap2_play_enabled = 1;
} else {
debug(2, "Connection %d: Stop playing.", conn->connection_number);
activity_monitor_signify_activity(0);
conn->ap2_play_enabled = 0;
activity_monitor_signify_activity(0);
reset_anchor_info(conn);
if (config.output->stop) {
#ifdef CONFIG_METADATA
send_ssnc_metadata('paus', NULL, 0, 1); // pause -- contains cancellation points
#endif
if (config.output->stop) {
debug(2, "Connection %d: Stop the output backend.", conn->connection_number);
config.output->stop();
}
@@ -3615,8 +3622,10 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// frame number. It seems that all frames up to but not
// including this frame are to be flushed.
//
// 'prsm' -- play stream resume. No arguments
// `pffr` -- the first frame of a play session has been received and has been validly
// 'prsm' -- play stream resume. No arguments. (deprecated)
// 'paus' -- buffered audio stream paused. No arguments.
// 'pres' -- buffered audio stream resumed. No arguments.
// 'pffr' -- the first frame of a play session has been received and has been validly
// timed.
// 'pvol' -- play volume. The volume is sent as a string --
// "airplay_volume,volume,lowest_volume,highest_volume"