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) }