Skip to content

Repository files navigation

pizeroDVD

Raspberry Pi Zero 2 W Standalone CD Player Setup Guide

Overview

This document captures the full end-to-end process for building a standalone headless CD music player using:

  • Raspberry Pi Zero 2 W
  • USB laptop CD/DVD drive
  • Bluetooth speaker
  • No monitor
  • No keyboard during normal usage
  • Optional future GPIO buttons for control

It documents the actual setup flow discussed and debugged, including:

  • Raspberry Pi OS setup
  • package installation
  • Bluetooth pairing and auto-reconnect
  • Audio output validation
  • CD drive detection
  • autoplay on disc insert
  • service and script files
  • troubleshooting steps
  • the shift from batch ripping to direct playback from the audio CD

Final Recommended Architecture

The final preferred design is:

  1. Raspberry Pi Zero 2 W boots headlessly.
  2. Pi reconnects automatically to the Bluetooth speaker.
  3. User inserts an audio CD and pushes the tray in.
  4. Linux detects the optical drive event.
  5. udev triggers a systemd service.
  6. The service runs an autoplay script.
  7. The script waits for the disc to become readable.
  8. The script starts direct CD playback using VLC.
  9. Audio plays through the Bluetooth speaker.

Hardware Used

Required Hardware

  • Raspberry Pi Zero 2 W
  • microSD card
  • 5V power supply for Pi
  • micro-USB OTG adapter
  • Powered USB hub
  • USB laptop-style CD/DVD drive
  • Bluetooth speaker
  • Wi-Fi network for setup
  • Another computer for SSH access

Why a Powered USB Hub Was Used

The Pi Zero 2 W has only one USB OTG data port and limited USB power budget. A slim optical drive can draw unstable current during spin-up and reads.

Using a powered USB hub helps avoid:

  • drive disconnects
  • random reboots
  • tray issues
  • unreliable disc reads

High-Level Flow Diagram

flowchart TD
    A[Power on Raspberry Pi Zero 2 W] --> B[Boot Raspberry Pi OS Lite]
    B --> C[Start Bluetooth reconnect service]
    C --> D[Reconnect Bluetooth speaker]
    D --> E[Wait for CD tray insert]
    E --> F[Audio CD inserted and tray pushed in]
    F --> G[udev detects /dev/sr0 change event]
    G --> H[systemd starts cdplayer.service]
    H --> I[autoplay_cd.sh runs]
    I --> J[Check /dev/sr0 exists]
    J --> K[Retry until audio CD is readable]
    K --> L[Set Bluetooth sink as default]
    L --> M[Launch VLC direct CD playback]
    M --> N[Music plays on Bluetooth speaker]
Loading

Sequential Setup

1. Flash Raspberry Pi OS Lite

Use Raspberry Pi OS Lite on the Pi Zero 2 W.

While flashing, enable:

  • SSH
  • Wi-Fi credentials
  • hostname
  • locale/timezone
  • username and password

Example hostname:

cdplayer

2. Boot the Pi and SSH into It

From another machine on the same network:

ping cdplayer.local
ssh pizero@cdplayer.local

If .local does not resolve, find the Pi IP from your router’s DHCP/connected devices page.


3. Update the Pi

sudo apt update
sudo apt full-upgrade -y

4. Install Required Packages

Install all required packages for:

  • Bluetooth management
  • Bluetooth audio
  • VLC playback
  • CD reading
  • tray control
  • future GPIO control
sudo apt install -y \
  bluez bluetooth \
  pipewire pipewire-pulse pipewire-audio pipewire-alsa pulseaudio-utils \
  wireplumber \
  vlc cdparanoia eject \
  python3 python3-pip python3-gpiozero

Why Each Package Is Used

Bluetooth packages

  • bluez
  • bluetooth

Used for:

  • pairing devices
  • trusting devices
  • reconnecting speaker
  • controlling Bluetooth from terminal using bluetoothctl

Audio stack packages

  • pipewire
  • pipewire-pulse
  • pipewire-audio
  • pipewire-alsa
  • pulseaudio-utils
  • wireplumber

Used for:

  • Bluetooth audio sink support
  • audio routing
  • pactl control
  • default output sink selection
  • headless user audio session

CD / playback packages

  • vlc

Used for:

  • headless audio playback with cvlc

  • direct audio CD playback

  • cdparanoia

Used for:

  • verifying the inserted disc is a readable audio CD

  • eject

Used for:

  • software tray eject / close / toggle

GPIO package

  • python3-gpiozero

Used later for optional physical buttons:

  • play/pause
  • next
  • previous
  • eject

5. Reboot

sudo reboot

Reconnect via SSH after reboot.


6. Confirm the CD Drive Is Visible

Connect the CD drive through:

  • Pi Zero 2 W USB data port
  • OTG adapter
  • powered USB hub
  • USB optical drive

Check for optical drive detection:

lsusb
dmesg | tail -n 80
ls /dev/sr*

Expected:

/dev/sr0

Why This Check Is Used

This confirms Linux can see the optical drive as a block CD device.

If /dev/sr0 does not exist, nothing else in the setup will work.


7. Pair the Bluetooth Speaker

Put the Bluetooth speaker in pairing mode.

Run:

bluetoothctl

Inside bluetoothctl:

power on
agent on
default-agent
scan on

Wait until the speaker appears.

Then run:

pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
scan off
exit

Replace the MAC address with your real speaker address.


Notes From Debugging

At one point the following happened:

Failed to set power on: org.bluez.Error.Failed
SetDiscoveryFilter failed: org.bluez.Error.NotReady
Failed to start discovery: org.bluez.Error.NotReady

This indicated Bluetooth controller readiness problems.

Commands used to debug Bluetooth readiness

sudo systemctl status bluetooth --no-pager
sudo systemctl restart bluetooth
rfkill list bluetooth
sudo rfkill unblock bluetooth
bluetoothctl list
bluetoothctl show

8. Verify Bluetooth Audio Sink

After pairing:

pactl list sinks short
pactl get-default-sink
bluetoothctl devices Connected

Expected sink format:

bluez_output.XX_XX_XX_XX_XX_XX.1

Set speaker as default output:

pactl set-default-sink bluez_output.XX_XX_XX_XX_XX_XX.1

9. Test Bluetooth Speaker Playback

Use a test sound:

paplay /usr/share/sounds/alsa/Front_Center.wav

If the test file is unavailable, use any existing WAV file.


10. Enable Persistent User Audio Session

Because the setup is headless and audio playback must work without desktop login, enable linger for the user:

sudo loginctl enable-linger pizero

Then start user audio services:

systemctl --user daemon-reload
systemctl --user enable --now pipewire pipewire-pulse wireplumber

Verify User Audio Services

systemctl --user status pipewire --no-pager
systemctl --user status pipewire-pulse --no-pager
systemctl --user status wireplumber --no-pager

11. Create Bluetooth Auto-Reconnect Script

Create:

nano /home/pizero/bt-reconnect.sh

Contents:

#!/bin/bash
MAC="XX:XX:XX:XX:XX:XX"

sleep 8

bluetoothctl <<EOF
power on
connect $MAC
EOF

sleep 3

SINK=$(pactl list sinks short | awk '/bluez_output/ {print $2; exit}')
if [ -n "$SINK" ]; then
  pactl set-default-sink "$SINK"
fi

Make executable:

chmod +x /home/pizero/bt-reconnect.sh

Why This Script Is Used

It ensures that after reboot the Pi:

  • powers Bluetooth on
  • reconnects to the previously paired speaker
  • resets the Bluetooth speaker as the default audio sink

12. Create Bluetooth Reconnect Service

Create:

sudo nano /etc/systemd/system/bt-reconnect.service

Contents:

[Unit]
Description=Reconnect Bluetooth Speaker
After=bluetooth.target network.target

[Service]
User=pizero
Type=oneshot
ExecStart=/home/pizero/bt-reconnect.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable bt-reconnect.service

Start it manually for testing:

sudo systemctl start bt-reconnect.service

Check logs/status:

sudo systemctl status bt-reconnect.service
sudo journalctl -u bt-reconnect.service -n 50 --no-pager

Why inactive (dead) Was Seen

Because the service is Type=oneshot, it runs once and exits. That is normal.

Adding:

RemainAfterExit=yes

makes it show as active/exited after success.


13. Initial CD Autoplay Attempt: Batch Ripping

The first working version used cdparanoia -B to batch rip all tracks, then start VLC.

This worked technically, but had a major user experience issue:

  • no music starts until ripping of all tracks finishes

This caused long silence before playback.


Early Batch-Rip Script Behavior

The CD insertion logs showed:

  • CD detection worked
  • ripping worked
  • playback failed because VLC was being launched as root

Example error seen:

VLC is not supposed to be run as root. Sorry.

Why That Happened

The original script was being run directly from udev, and udev runs commands as root.

VLC refused to run as root, and root does not use the normal user’s Bluetooth audio session.

This led to two fixes:

  1. launch long work via systemd instead of directly inside udev
  2. run VLC as user pizero using runuser

14. Better Trigger Method: Use udev to Start systemd Service

Create or update:

sudo nano /etc/udev/rules.d/99-cd-autoplay.rules

Contents:

SUBSYSTEM=="block", KERNEL=="sr0", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}="cdplayer.service"

Reload rules:

sudo udevadm control --reload-rules
sudo udevadm trigger

Why This Rule Is Used

udev is good for detecting hardware events.

systemd is better for running:

  • longer scripts
  • logging
  • retries
  • user-aware commands

So udev only triggers systemd, and systemd runs the CD playback logic.


15. Create cdplayer.service

Create:

sudo nano /etc/systemd/system/cdplayer.service

Contents:

[Unit]
Description=CD Autoplay Service
After=bluetooth.target

[Service]
Type=oneshot
ExecStart=/home/pizero/cdplayer/autoplay_cd.sh

Reload:

sudo systemctl daemon-reload

16. Final Recommended Playback Strategy

Chosen Final Strategy

Direct playback from the Audio CD using VLC

This was chosen over batch ripping because:

  • it starts music much faster
  • it feels more like a real CD player
  • it removes the long ripping delay
  • it simplifies file management

Why cdparanoia Is Still Kept

Even with direct playback, cdparanoia -Q is still useful to verify:

  • the disc is present
  • the disc is actually a readable audio CD
  • the drive is ready before playback starts

17. Create Final Direct-CD Autoplay Script

Create directory structure if missing:

mkdir -p /home/pizero/cdplayer/logs

Create:

nano /home/pizero/cdplayer/autoplay_cd.sh

Contents:

#!/bin/bash

USER_NAME="pizero"
USER_HOME="/home/$USER_NAME"
USER_ID=$(id -u "$USER_NAME")

WORKDIR="$USER_HOME/cdplayer"
LOG="$WORKDIR/logs/cdplayer.log"
LOCKFILE="/tmp/cdplayer.lock"

mkdir -p "$WORKDIR/logs"

if [ -f "$LOCKFILE" ]; then
  echo "$(date) : already running, exiting" >> "$LOG"
  exit 0
fi

touch "$LOCKFILE"
trap 'rm -f "$LOCKFILE"' EXIT

echo "----------------------------------------" >> "$LOG"
echo "$(date) : CD event received" >> "$LOG"

if [ ! -e /dev/sr0 ]; then
  echo "$(date) : ERROR - /dev/sr0 not found" >> "$LOG"
  exit 1
fi

echo "$(date) : Drive found" >> "$LOG"

# Wait until the disc is actually readable
DISC_READY=0
for i in {1..8}; do
  echo "$(date) : Checking disc readiness (attempt $i/8)" >> "$LOG"
  cdparanoia -Q -d /dev/sr0 >> "$LOG" 2>&1
  STATUS=$?
  if [ $STATUS -eq 0 ]; then
    DISC_READY=1
    break
  fi
  sleep 3
done

if [ $DISC_READY -ne 1 ]; then
  echo "$(date) : ERROR - disc is not readable as audio CD after retries" >> "$LOG"
  exit 1
fi

echo "$(date) : Audio CD detected" >> "$LOG"

# Stop previous playback
pkill -u "$USER_NAME" -f "vlc.*cdda" >/dev/null 2>&1
sleep 1

# Ensure Bluetooth sink is still available
SINK=$(runuser -u "$USER_NAME" -- env \
  XDG_RUNTIME_DIR="/run/user/$USER_ID" \
  DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \
  PULSE_SERVER="unix:/run/user/$USER_ID/pulse/native" \
  pactl list sinks short | awk '/bluez_output/ {print $2; exit}')

if [ -n "$SINK" ]; then
  runuser -u "$USER_NAME" -- env \
    XDG_RUNTIME_DIR="/run/user/$USER_ID" \
    DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \
    PULSE_SERVER="unix:/run/user/$USER_ID/pulse/native" \
    pactl set-default-sink "$SINK"
  echo "$(date) : Default sink set to $SINK" >> "$LOG"
else
  echo "$(date) : WARNING - No Bluetooth sink found, using current default output" >> "$LOG"
fi

echo "$(date) : Starting direct CD playback" >> "$LOG"

runuser -u "$USER_NAME" -- env \
  XDG_RUNTIME_DIR="/run/user/$USER_ID" \
  DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \
  PULSE_SERVER="unix:/run/user/$USER_ID/pulse/native" \
  cvlc cdda:///dev/sr0 >> "$LOG" 2>&1 &

sleep 2

if pgrep -u "$USER_NAME" -f "vlc.*cdda" >/dev/null 2>&1; then
  echo "$(date) : Playback launched successfully" >> "$LOG"
else
  echo "$(date) : ERROR - Playback process did not start" >> "$LOG"
fi

exit 0

Make it executable:

chmod +x /home/pizero/cdplayer/autoplay_cd.sh

Why This Script Is Used

This script does all of the following in order:

  1. prevents duplicate launches with a lock file
  2. confirms /dev/sr0 exists
  3. retries until the audio CD is actually readable
  4. stops any previous direct-CD playback
  5. re-applies the Bluetooth speaker sink
  6. launches VLC as user pizero
  7. logs success or failure

18. Manual Direct Playback Test

Before relying on insert-trigger automation, manually test direct playback:

runuser -u pizero -- env \
XDG_RUNTIME_DIR="/run/user/$(id -u pizero)" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u pizero)/bus" \
PULSE_SERVER="unix:/run/user/$(id -u pizero)/pulse/native" \
cvlc cdda:///dev/sr0

If this works, the direct playback audio path is correct.


19. Manual Script Test

With an audio CD inserted:

/home/pizero/cdplayer/autoplay_cd.sh

Check:

tail -n 100 /home/pizero/cdplayer/logs/cdplayer.log
ps aux | grep vlc

Expected successful log flow:

CD event received
Drive found
Checking disc readiness...
Audio CD detected
Default sink set to ...
Starting direct CD playback
Playback launched successfully

20. Real Insert-Tray Test

After setting everything up:

  1. Open tray
  2. Insert audio CD
  3. Push tray in
  4. Wait a few seconds

Then inspect:

sudo journalctl -u cdplayer.service -n 50 --no-pager
tail -n 100 /home/pizero/cdplayer/logs/cdplayer.log
ps aux | grep vlc

21. Testing Commands Summary

Network / access

ping cdplayer.local
ssh pizero@cdplayer.local

CD drive visibility

lsusb
dmesg | tail -n 80
ls /dev/sr*

Bluetooth readiness

sudo systemctl status bluetooth --no-pager
sudo systemctl restart bluetooth
rfkill list bluetooth
sudo rfkill unblock bluetooth
bluetoothctl list
bluetoothctl show

Bluetooth pairing

bluetoothctl

Then:

power on
agent on
default-agent
scan on
pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
scan off
exit

Audio sink checks

pactl list sinks short
pactl get-default-sink
bluetoothctl devices Connected

Bluetooth speaker playback test

paplay /usr/share/sounds/alsa/Front_Center.wav

User audio services

systemctl --user status pipewire --no-pager
systemctl --user status pipewire-pulse --no-pager
systemctl --user status wireplumber --no-pager

Bluetooth reconnect service

sudo systemctl start bt-reconnect.service
sudo systemctl status bt-reconnect.service
sudo journalctl -u bt-reconnect.service -n 50 --no-pager

Direct CD playback test

runuser -u pizero -- env \
XDG_RUNTIME_DIR="/run/user/$(id -u pizero)" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u pizero)/bus" \
PULSE_SERVER="unix:/run/user/$(id -u pizero)/pulse/native" \
cvlc cdda:///dev/sr0

Manual autoplay script test

/home/pizero/cdplayer/autoplay_cd.sh

Service / log checks

sudo journalctl -u cdplayer.service -n 50 --no-pager
tail -n 100 /home/pizero/cdplayer/logs/cdplayer.log
ps aux | grep vlc

Tray control

Open tray:

eject /dev/sr0

Close tray:

eject -t /dev/sr0

Toggle tray:

eject -T /dev/sr0

Stop current VLC playback:

pkill -u pizero -f "vlc.*cdda"

22. Problems Encountered and Final Resolutions

Problem 1: Bluetooth NotReady

Symptoms

  • power on failed
  • scan on failed
  • controller not ready

Fix

  • restart Bluetooth service
  • check rfkill
  • unblock Bluetooth
  • verify controller with bluetoothctl list/show

Problem 2: Bluetooth sink command typo

Incorrect

pactl list sink start

Correct

pactl list sinks short

Problem 3: User service typo

Incorrect

pipewire-pluse

Correct

pipewire-pulse

Problem 4: VLC refused to run

Symptom

VLC is not supposed to be run as root

Cause

udev launched script as root, so VLC was run as root.

Fix

  • trigger systemd from udev
  • run cvlc as pizero using runuser
  • use user audio session environment variables

Problem 5: Batch ripping caused long delay

Symptom

No sound until all tracks had ripped.

Fix

Move to direct CD playback:

cvlc cdda:///dev/sr0

Problem 6: Early disc-read failures

Symptom

Logs initially showed:

  • Unable to read table of contents header
  • disc is not readable as audio CD

Cause

The tray event fired before the disc was fully ready.

Fix

Add retry loop using:

cdparanoia -Q -d /dev/sr0

until the disc becomes readable.


23. Final End-to-End Boot and Playback Sequence

On boot

  1. Pi boots.
  2. bt-reconnect.service runs.
  3. Bluetooth speaker reconnects.
  4. Speaker sink becomes default output.

On disc insert

  1. Tray is pushed in.
  2. /dev/sr0 emits a block change event.
  3. udev tells systemd to start cdplayer.service.
  4. cdplayer.service runs /home/pizero/cdplayer/autoplay_cd.sh.
  5. Script waits until the audio CD becomes readable.
  6. Script sets the Bluetooth sink if present.
  7. Script launches cvlc cdda:///dev/sr0 as user pizero.
  8. Music starts playing.

24. Optional Future Enhancements

Not implemented yet, but discussed as future improvements:

  • GPIO buttons for:
    • play/pause
    • next track
    • previous track
    • eject
  • status LED
  • transparent enclosure-mounted controls
  • software eject button
  • custom acrylic / transparent frame build

Possible GPIO mapping later:

  • GPIO17 → play/pause
  • GPIO27 → next
  • GPIO22 → previous

25. Final Recommended Files Checklist

You should end up with these key files:

/home/pizero/bt-reconnect.sh
/home/pizero/cdplayer/autoplay_cd.sh
/etc/systemd/system/bt-reconnect.service
/etc/systemd/system/cdplayer.service
/etc/udev/rules.d/99-cd-autoplay.rules

26. Final Recommendation

For this project, the best current setup is:

  • Pi Zero 2 W
  • powered USB hub
  • USB laptop CD drive
  • Bluetooth speaker
  • direct audio CD playback using VLC
  • udevsystemd trigger flow
  • Bluetooth auto-reconnect on boot

This gives the best balance of:

  • fast startup
  • simpler user experience
  • fewer delays than ripping
  • clean headless operation

About

Raspberry Pi Zero 2 W CD player with Bluetooth audio and automatic CD playback.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages