Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker image, pyalsaaudio, audio device etc. #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
29 changes: 29 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Build and Push Docker Image

on:
push:
branches: master

jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: ./
file: ./docker/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64
push: true
tags: charlesomer/airplay:latest
153 changes: 152 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,116 @@
events.bin
proto
Features.xlsx
airplay-env

# Created by https://www.toptal.com/developers/gitignore/api/python,vscode,macos,windows,linux,c
# Edit at https://www.toptal.com/developers/gitignore?templates=python,vscode,macos,windows,linux,c

### C ###
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
Expand Down Expand Up @@ -50,6 +156,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log

# Translations
*.mo
Expand All @@ -70,6 +177,7 @@ instance/

# Sphinx documentation
docs/_build/
doc/_build/

# PyBuilder
target/
Expand Down Expand Up @@ -109,6 +217,7 @@ venv/
ENV/
env.bak/
venv.bak/
pythonenv*

# Spyder project settings
.spyderproject
Expand All @@ -127,3 +236,45 @@ dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# profiling data
.prof

### vscode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/python,vscode,macos,windows,linux,c
54 changes: 38 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,64 @@ Next steps:
- FairPlay v2 Support
---

## Raspberry Pi 4
## Pre-Built Docker Image
This image is built directly from `master` so may break. Tested with Raspberry Pi.

Install docker and then build the image:
https://hub.docker.com/r/charlesomer/airplay

Example Docker Compose
```zsh
docker build -f docker/Dockerfile -t invano/ap2-receiver .
version: "3.8"
services:
airplay:
image: charlesomer/airplay:latest
restart: always
network_mode: host
environment: # All variables are optional.
# - AP2HOSTNAME=Airplay2Device
# - AP2IFACE=eth0
# - AUDIO_DEVICE=default # For use with alsaaudio.
# - DISABLE_PORTAUDIO=true
# - NO_VOLUME_MANAGEMENT=true
devices:
- "/dev/snd"
```

To run the receiver:
## Raspberry Pi

Install docker and then build the image:

```zsh
docker run -it --rm --device /dev/snd --net host invano/ap2-receiver
docker build -f docker/Dockerfile -t USERNAME/airplay .
```

Default network device is wlan0, you can change this with AP2IFACE env variable:
To run the receiver:

```zsh
docker run -it --rm --device /dev/snd --env AP2IFACE=eth0 --net host invano/ap2-receiver
docker run -it --rm --device /dev/snd --net host USERNAME/airplay
```

## macOS Catalina

To run the receiver please use Python 3 and do the following:
## macOS

* Run the following commands
_macOS has shown issues when playing audio, if anyone is able to take a look at this to confirm/fix that would be great._

Currently `portaudio` is required for MacOS. It can be installed via homebrew:
```zsh
brew install python3
brew install portaudio
virtualenv -p /usr/local/bin/python3 proto
source proto/bin/activate
pip install -r requirements.txt
pip install --global-option=build_ext --global-option="-I/usr/local/Cellar/portaudio/19.6.0/include" --global-option="-L/usr/local/Cellar/portaudio/19.6.0/lib" pyaudio
```
Then, you may be able to use the docker image although this is untested. Add the `--use-portaudio` option. Alternatively, clone the repo and run via python virtualenv.

```zsh
pip3 install virtualenv
virtualenv -p /usr/local/bin/python3 airplay-env
source airplay-env/bin/activate
pip3 install -r requirements.txt

# The following line may not be required.
# pip3 install --global-option=build_ext --global-option="-I/usr/local/Cellar/portaudio/19.6.0/include" --global-option="-L/usr/local/Cellar/portaudio/19.6.0/lib" pyaudio

python ap2-receiver.py -m myap2 --netiface=en0
# Allow incoming connections.
```

## Windows
Expand Down
18 changes: 13 additions & 5 deletions ap2-receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
HTTP_CT_IMAGE = "image/jpeg"
HTTP_CT_DMAP = "application/x-dmap-tagged"

AUDIO_DEVICE = "default"
USE_PORTAUDIO = True

def setup_global_structs(args):
global sonos_one_info
global sonos_one_setup
Expand Down Expand Up @@ -116,7 +119,7 @@ def setup_global_structs(args):
if DISABLE_VM:
volume = 0
else:
volume = get_volume()
volume = get_volume(AUDIO_DEVICE)
second_stage_info = {
"initialVolume": volume,
}
Expand Down Expand Up @@ -291,7 +294,7 @@ def do_SETUP(self):
else:
print("Sending CONTROL/DATA:")

stream = Stream(plist["streams"][0])
stream = Stream(plist["streams"][0], AUDIO_DEVICE, USE_PORTAUDIO)
self.server.streams.append(stream)
sonos_one_setup_data["streams"][0]["controlPort"] = stream.control_port
sonos_one_setup_data["streams"][0]["dataPort"] = stream.data_port
Expand Down Expand Up @@ -327,7 +330,7 @@ def do_GET_PARAMETER(self):
if p == b"volume":
print("GET_PARAMETER: %s" % p)
if not DISABLE_VM:
params_res[p] = str(get_volume()).encode()
params_res[p] = str(get_volume(AUDIO_DEVICE)).encode()
else:
print("Volume Management is disabled")
else:
Expand All @@ -342,7 +345,7 @@ def do_GET_PARAMETER(self):
self.send_header("Server", self.version_string())
self.send_header("CSeq", self.headers["CSeq"])
self.end_headers()
hexdump(res);
hexdump(res)
self.wfile.write(res)

def do_SET_PARAMETER(self):
Expand All @@ -361,7 +364,7 @@ def do_SET_PARAMETER(self):
if pp[0] == b"volume":
print("SET_PARAMETER: %s => %s" % (pp[0], pp[1]))
if not DISABLE_VM:
set_volume(float(pp[1]))
set_volume(float(pp[1]), AUDIO_DEVICE)
else:
print("Volume Management is disabled")
elif pp[0] == b"progress":
Expand Down Expand Up @@ -711,6 +714,8 @@ def upgrade_to_encrypted(self, client_address, shared_key):
parser.add_argument("-m", "--mdns", required=True, help="mDNS name to announce")
parser.add_argument("-n", "--netiface", required=True, help="Network interface to bind to")
parser.add_argument("-nv", "--no-volume-management", required=False, help="Disable volume management", action='store_true')
parser.add_argument("-d", "--audio-device", required=False, help="Specify output device (string).", default='default')
parser.add_argument("-dpo", "--disable-portaudio", required=False, help="Disable portaudio.", default=False)
parser.add_argument("-f", "--features", required=False, help="Features")

args = parser.parse_args()
Expand All @@ -719,6 +724,9 @@ def upgrade_to_encrypted(self, client_address, shared_key):
IFEN = args.netiface
ifen = ni.ifaddresses(IFEN)
DISABLE_VM = args.no_volume_management

AUDIO_DEVICE = args.audio_device
USE_PORTAUDIO = not args.disable_portaudio
if args.features:
try:
FEATURES = int(args.features, 16)
Expand Down
Loading