2018-10-24 16:56:35 +00:00
|
|
|
package sonar
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"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))
|
2018-10-24 17:00:28 +00:00
|
|
|
// rand.Shuffle(len(files), func(i, j int) {
|
|
|
|
// files[i], files[j] = files[j], files[i]
|
|
|
|
// })
|
2018-10-24 16:56:35 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|