src/croaker | ||
test | ||
.coveragerc | ||
.gitignore | ||
LICENSE | ||
pyproject.toml | ||
README.md |
Croaker
Croaker is a Linux desktop audio player controlled from a TCP server. It is designed specifically to play background music during TTRPG sessions.
Features
- Audio playback using VLC
- Playlists are built using symlinks
- Randomizes playlist order the first time it is cached
- Always plays
_theme.mp3
first upon switching to a playlist, if it exists - Controlled by issuing commands over a TCP socket
Requirements
- A functioning shoutcast / icecast server
- Python >= 3.11
- python3.11-dev
What? Why?
I run an online D&D game. For years I have provided my players with an internet radio station playing the session background music. The first version was built using liquidsoap and icecast. The second version replaced liquidsoap with a custom streamer implementation (which is still available on the shoutcast
branch, warts and all).
Both of these solutions were functional but high maintenance, and I wanted something simpler both for me and my players.
This version of Croaker usees VLC (via python-vlc) to play audio locally, and pops up a read-only desktop interface to display what is playing. I share this app using screen sharing during our online games, and control it using my DM tools.
Now that is a powerful yak! -- Aesop Rock (misquoted)
Quick Start (Server)
This assumes you have a functioning icecast2/whatever installation already.
% mkdir -p ~/.dnd/croaker
% croaker setup > ~/.dnd/croaker/defaults
% vi ~/.dnd/croaker/defaults # adjust to taste
% croaker add session_start /music/session_start.mp3
% croaker add battle /music/battle/*.mp3
Now start the server, which will begin streaming the session_start
playlist:
Controlling The Server
% croaker start
INFO Daemonizing controller on (localhost, 8003); pidfile and logs in ~/.dnd/croaker
Connnect to the command & control server:
% telnet localhost 8003
Trying 127.0.0.1...
Connected to croaker.local.
Escape character is '^]'.
help
PLAY PLAYLIST - Load and play the specified playlist.
LIST [PLAYLIST] - List all lplaylists or the contents of a single playlist.
BACK - Return to the previous track in the playlist
FFWD - Skip to the next track in the playlist.
HELP - Display command help.
KTHX - Close the current connection.
STOP - Stop the current track and stream silence.
STFU - Terminate the Croaker server.
List available playlists:
list
battle
adventure
session_start
Switch to battle music -- roll initiative!
play battle
OK
Skip this track and move on to the next:
ffwd
OK
Stop the music:
stop
OK
Disconnect:
kthx
KBAI
Connection closed by foreign host.
Python Client Implementation
Here's a sample client using Ye Olde Socket Library:
import socket
from dataclasses import dataclass
from functools import cached_property
@dataclass
class CroakerClient():
host: str
port: int
@cached_property
def playlists(self):
return self.send("LIST").split("\n")
def list(self, *args):
if not args:
return self.playlists
return self.send(f"LIST {args[0]}")
def play(self, *args):
if not args:
return "Error: Must specify the playlist to play."
return self.send(f"PLAY {args[0]}")
def ffwd(self, *args):
return self.send("FFWD")
def stop(self, *args):
return self.send("STOP")
def send(self, msg: str):
BUFSIZE = 4096
data = bytearray()
with socket.create_connection((self.host, self.port)) as sock:
sock.sendall(f"{msg}\n".encode())
while True:
buf = sock.recv(BUFSIZE)
data.extend(buf)
if len(buf) < BUFSIZE:
break
sock.sendall(b'KTHX\n')
return data.decode()
if __name__ == '__main__':
client = CroakerClient(host='localhost', port=1234)
client.play('session_start')