7

I'm working on a small application to ring school bells on a schedule, which can be updated from a website. Everything is working great, except the script that is scheduled as a cron job won't play the sound when the script is run. I have added output piping and echo commands to the script to verify that cron is running it, but the part that plays the sound doesn't work. The script works as expected when run manually from CLI.

The script extracts a time and a sound file for each period of the day on the schedule, then compares the time associated to the sound file with the current time - if it's a match, it will

exec("/usr/bin/aplay /var/www/site/".$soundfile);

Cron is then scheduled to run this script every minute during the school day:

* 8-16 * 1-6,9-12 1-5 root /usr/bin/php -f /var/www/site/scripts/playsound.php > /dev/null

Again, if I manually run the script when there is sound scheduled, the sound plays through the attached speakers. When I have test code that will echo to the screen or output to a file entered as well, cron will dump the output to the files, confirming it is running the script as scheduled. It just won't play the darn sound part of the script.

I've checked all my permissions and since everything else works, they seem to be accurate. I can even write a simple BASH script to get Cron to play a sound on a schedule, so it seems the system has the right group memberships to access both script and sound file. I have switched out exec() for shell_exec(), tried using just the commands as well as the absolute paths to the commands, and the cron job is scheduled to run as root. Still can't figure out why this one small feature that is unfortunately so critical to this program succeeding won't work.

Any advice is greatly appreciated.

fejese
  • 4,601
  • 4
  • 29
  • 36
  • This seems more like a question for a different SE site. For Ubuntu there's an answer here: http://askubuntu.com/questions/530048/ubuntu-14-04-and-playing-songs-from-cron Could be helpful on other nix flavors maybe. – developerwjk Jan 08 '15 at 23:35
  • 1
    Try adding this to the exec: `exec('... >/tmp/cronlog 2>&1')` and check the `/tmp/cronlog` log file if there's any error. If `aplay` needs X you need to make sure that root has access to the active X session (see `xhost` command). You might want to use a user that has active session for the cron job. – fejese Jan 08 '15 at 23:40
  • This might help: http://stackoverflow.com/a/22744360/1163786 - One advise: do not suppress errors while debugging cronjob problems. Raise logging level. – Jens A. Koch Jan 08 '15 at 23:44
  • 1
    Sometimes things run via cron or job queues don't get the environment variables you have in your usual console environment. Try `PATH= HOME= /usr/bin/aplay /var/www/site/` at the console to see if that stops things working (this emulates those two vars not being available). If so, try each in turn to see which one. – halfer Jan 09 '15 at 00:38
  • is that php file just several lines? could you just publish that fragment with echos. does echo determinate that cron job runs the php script at scheduled time? – Alex Jan 09 '15 at 02:22
  • I find it is indeed very advisable to never discard scripts' output by redirecting it to /dev/null. If you do not want to waste much space in logs, log to some file (without appending), that will contain the latest invocation's results. ./script > $TMP/last_script.log – Raúl Salinas-Monteagudo Mar 24 '15 at 17:22

4 Answers4

1

Probably permissions on /dev/snd/pcmC0D0p are the problem. (That's the device for ALSA Card 0: Device 0: playback.) If there is a desktop session running on the server, it may have a pulseaudio daemon holding the device open.

Setting things up for multiple users to be able to play sound at the same time is a mess, and requires configuring pulseaudio in a low-performance mode that isn't zero-copy, so don't do that. Just make sure the server can exclusively open the sound device when needed.

To see whether an ALSA sound device is open or not (regardless of paused state or w/e):

$ cat /proc/asound/card0/pcm0p/sub0/hw_params 
access: MMAP_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 8192
buffer_size: 16384

$ cat /proc/asound/card0/pcm1p/sub0/hw_params 
closed

So the first playback PCM on my first sound card is open, but the 2nd PCM (the S/PDIF output of my motherboard audio) is closed. Further opens of the sound device only work because the default ALSA setup makes the "default" device a pulseaudio wrapper. hw:0 would fail, because it's busy, and this sound card doesn't have a hardware mixer.

(Fun fact: some old soundcards, e.g. some PCI Soundblaster cards, supported multiple opens of the hardware device. Multiple streams of PCM data could be DMAed to the card, where they would be mixed by its DSP. Unless I'm totally wrong and the kernel driver was mixing >.< But anyway, you didn't need pulseaudio if you had one.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
1

As I understand it, cron | crontab runs in it's own environment; e.g., see accepted answer in:

https://serverfault.com/questions/337631/crontab-execution-doesnt-have-the-same-environment-variables-as-executing-user

Thus, even though aplay will play a sound (WAV) file when invoked as a regular user or as root (su | sudo), it won't play that file when it is called from cron -- e.g., executing a bash (.sh) script that contains your aplay statement).

This works, on my Arch Linux system.

Here is my cron_notification.sh script:

#!/usr/bin/bash

# /mnt/Vancouver/Programming/scripts/cron_notification.sh

# For use with crontab [ sudo gedit /etc/crontab ]
# cron (Arch Linux: cronie) requires full paths:
#   /usr/bin/aplay
#   /usr/bin/notify-send

for i in 1 2 3 4 5
  do
    #aplay alarm.mp3    ## << aplay cannot play MP3 files; use WAV
    # ----------------------------------------
    # NEEDED TO RUN 'aplay' FROM crontab:
    # https://unix.stackexchange.com/questions/231941/cant-run-aplay-as-root
    # https://www.reddit.com/r/linuxquestions/comments/37vcbo/playing_audio_from_a_cronjob/
    # PulseAudio needs XDG_RUNTIME_DIR, so:

    XDG_RUNTIME_DIR=/run/user/`id -u` /usr/bin/aplay /mnt/Vancouver/Programming/scripts/PHASER.WAV
    # ----------------------------------------
    sleep 0.25
done

# ----------------------------------------------------------------------------
# "Critical" alerts persist until clicked (i.e., do not appear, then fade after ~20"):
# notify-send -u critical 'Hello Victoria!' 'Countdown has ended!' --icon=dialog-information

/usr/bin/notify-send -u critical 'Hello Victoria!' "It's 3 pm!" -i /mnt/Vancouver/Programming/scripts/alert.jpg

And, here is the relevant portion of my /etc/crontab file:

# /etc/crontab

# system-wide crontab
# edit: sudo {your favorite text editor: gedit; geany; ...} /etc/crontab
# https://crontab.guru      ## online crontab values checker, planner

# =====================================================================
# SHELL
# =====================================================================

# cron (crontab) requires full paths:
SHELL=/usr/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# =====================================================================
# ARCH LINUX-RELATED [Arch Linux x86_64]
# =====================================================================

# cron | https://wiki.archlinux.org/index.php/Cron
# Will install, use "cronie" cron (crontab):
# Install cronie:  sudo pacman -Syu cronie
#    Enable cron:  sudo systemctl enable --now cronie.service
#     Start cron:  sudo systemctl start cronie.service
#   Restart cron:  sudo systemctl restart cronie.service

# =====================================================================
# APLAY NOTIFICATION (3:00pm M-F)
# =====================================================================

# Two issues when running
#   /mnt/Vancouver/Programming/scripts/cron_notification.sh
# from cron:

# ----------------------------------------
# 1. "notify-send":

# https://bbs.archlinux.org/viewtopic.php?id=216912
# notify-send also needs access to your DBUS_SESSION_BUS_ADDRESS. Assuming that your UID is 1000:

DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"

# More here: https://wiki.archlinux.org/index.php/Desktop_notifications#Usage_in_programming

# ----------------------------------------
# 2. "aplay":

# TO RUN 'aplay' FROM crontab:
# https://www.reddit.com/r/linuxquestions/comments/37vcbo/playing_audio_from_a_cronjob/
# PulseAudio needs XDG_RUNTIME_DIR, so:

# XDG_RUNTIME_DIR=/run/user/`id -u` /usr/bin/aplay /mnt/Vancouver/Programming/scripts/PHASER.WAV

# Line above added to "/mnt/Vancouver/Programming/scripts/cron_notification.sh" script.

# ----------------------------------------

# m    h    dom    mon    dow    user    nice    command

# "At 15:00 on every day-of-week from Monday through Friday”
# [https://crontab.guru/#0_15_*_*_1-5]:
0    15    *    *    1-5    victoria    nice -n 19    /usr/bin/bash /mnt/Vancouver/Programming/scripts/cron_notification.sh

# NOTE -- running as user ("victoria"), not "root".

# Test - every minute:
#*    *    *    *    1-5    victoria    nice -n 19    /usr/bin/bash /mnt/Vancouver/Programming/scripts/cron_notification.sh

I left the comments in place, for references and clarity.

alert.jpg

notification

You can download the PHASER.WAV sound file from my website, here:

http://persagen.com/files/PHASER.WAV

Victoria Stuart
  • 4,610
  • 2
  • 44
  • 37
0

Instead of setting as cron, use php interval or javascript (asynchronous - ajax) to call the intended php file. I think this will resolve your issue.

The cron job executing in server, it will not throw any output to your browser.

Cron Job is good to send notifications/mails, update/insert into db in particular intervals.

For your requirements, you have to run the script through browser.

Math
  • 2,399
  • 2
  • 20
  • 22
Binish Prabhakar
  • 84
  • 1
  • 1
  • 11
0

As others have mentioned in the comments you really must save the logs and then examine them for clues.

From what I recall from my experiences using a dedicated script which is executed by PHP can be beneficial. You would feed the bash script the name of the sound file as a parameter. For example:

exec("/path/to/script.sh $SOUNDFILEPATH");

In the script you can setup your working directory, environment variables, etc.

Of course, you must escape variables if you want to avoid the students getting mischievous ideas.

One technique which I've always fallen back on when the going gets tough is to have one script running under cron which is controlled by a file stored in say /tmp/ which you can write to via the web server and PHP.

So just get PHP to write control data to a file in /tmp (or any other writable directory) and get your tested user bash script to monitor it regularly via cron. It could be as simple as having one line that points to the sound file in your case.

That's always worked for me with no surprises.

zaf
  • 22,776
  • 12
  • 65
  • 95