Raspberry Pi alarm clock server.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

181 lines
3.7 KiB

package sonar
import (
"errors"
"fmt"
"math/rand"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
// Player manages playing music one track at a time.
type Player struct {
index int
playlist []string // shuffled file array
cmd *exec.Cmd
}
// Run the playlist.
func (p *Player) Run(playlist []string) {
p.playlist = playlist
p.index = 0
}
// Stop the playlist.
func (p *Player) Stop() error {
if p.cmd == nil {
return errors.New("no active player command")
}
err := p.cmd.Process.Kill()
if err != nil {
return fmt.Errorf("StopPlaylist: kill: %s", err)
}
p.cmd = nil
p.index = 0
return nil
}
// Next returns the next song.
func (p *Player) Next() (string, bool) {
p.index++
if p.index < len(p.playlist) {
return p.playlist[p.index], true
}
return "", false
}
// Status prints the playlist status.
func (p *Player) Status() string {
var parts []string
if p.Running() {
parts = append(parts, "Running")
var (
index = p.index
length = len(p.playlist)
pct float64
)
if length > 0 {
pct = float64(index / length)
}
parts = append(parts, fmt.Sprintf(
"Track %d of %d (%.2f%%)",
index,
length,
pct,
))
// Append current track.
if index < len(p.playlist) {
parts = append(parts, fmt.Sprintf(
"Now playing: %s",
p.playlist[index],
))
}
} else {
parts = append(parts, "Not running")
}
return strings.Join(parts, "; ")
}
// Running returns whether the playlist is actively running.
func (p *Player) Running() bool {
return p.cmd != nil
}
// Volume returns the current volume as an int between 0 and 100.
func (s *Sonar) Volume() int {
args := s.Config.VolumeStatusCommand
out, err := exec.Command(args[0], args[1:]...).Output()
if err != nil {
log.Error(err.Error())
}
volume := strings.Trim(string(out), "%\n\t ")
a, err := strconv.Atoi(volume)
if err != nil {
log.Error("Volume: %s", err.Error())
}
return a
}
// VolumeUp turns up the volume.
func (s *Sonar) VolumeUp() error {
args := s.Config.VolumeUpCommand
return exec.Command(args[0], args[1:]...).Run()
}
// VolumeDown turns down the volume.
func (s *Sonar) VolumeDown() error {
args := s.Config.VolumeDownCommand
return exec.Command(args[0], args[1:]...).Run()
}
// VolumeMute toggles muted volume.
func (s *Sonar) VolumeMute() error {
args := s.Config.VolumeMuteCommand
return exec.Command(args[0], args[1:]...).Run()
}
// StartPlaylist starts the playlist.
func (s *Sonar) StartPlaylist() error {
if s.Player.Running() {
log.Error("StartPlaylist: already running")
return errors.New("playlist is already running")
}
// Find the media files.
files, err := filepath.Glob(filepath.Join(s.Config.MediaPath, "*.*"))
if err != nil {
return fmt.Errorf("Can't glob %s/*.*: %s", s.Config.MediaPath, err)
}
// Shuffle the files.
log.Debug("StartPlaylist: shuffled %d media files", len(files))
rand.Shuffle(len(files), func(i, j int) {
files[i], files[j] = files[j], files[i]
})
// Install the playlist and go.
s.Player.Run(files)
s.PlayNext()
return nil
}
// StopPlaylist kills the playlist.
func (s *Sonar) StopPlaylist() error {
return s.Player.Stop()
}
// Play a track.
func (s *Sonar) Play(filename string) error {
log.Info("Play: %s", filename)
args := make([]string, len(s.Config.MediaCommand))
for i, part := range s.Config.MediaCommand {
args[i] = strings.Replace(part, "%s", filename, -1)
}
log.Debug("Exec: %v", args)
cmd := exec.Command(args[0], args[1:]...)
if err := cmd.Start(); err != nil {
return err
}
s.Player.cmd = cmd
return nil
}
// PlayNext track.
func (s *Sonar) PlayNext() error {
filename, ok := s.Player.Next()
if !ok {
return errors.New("end of playlist")
}
return s.Play(filename)
}