diff --git a/.drone.yml b/.drone.yml index 9551cff..894af72 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,6 +47,17 @@ steps: event: - tag +- name: gitea release + image: plugins/gitea-release + settings: + api_key: + from_secret: gitea_api_key + base_url: https://git.nemunai.re/ + files: deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + when: + event: + - tag + - name: docker image: plugins/docker settings: diff --git a/api/alarm.go b/api/alarm.go index 7b1e9d0..071073d 100644 --- a/api/alarm.go +++ b/api/alarm.go @@ -33,6 +33,17 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) { c.JSON(http.StatusOK, true) }) + router.POST("/alarm/next", func(c *gin.Context) { + if player.CommonPlayer == nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No player currently playing"}) + return + } else { + player.CommonPlayer.NextTrack() + } + + c.JSON(http.StatusOK, true) + }) + router.DELETE("/alarm", func(c *gin.Context) { if player.CommonPlayer != nil { err := player.CommonPlayer.Stop() diff --git a/player/player.go b/player/player.go index 2162fd1..a86bfcc 100644 --- a/player/player.go +++ b/player/player.go @@ -6,19 +6,12 @@ import ( "math" "math/rand" "os" + "os/exec" "os/signal" - "path" "strings" "syscall" "time" - "github.com/faiface/beep" - "github.com/faiface/beep/effects" - "github.com/faiface/beep/flac" - "github.com/faiface/beep/mp3" - "github.com/faiface/beep/speaker" - "github.com/faiface/beep/wav" - "git.nemunai.re/nemunaire/reveil/config" "git.nemunai.re/nemunaire/reveil/model" ) @@ -26,11 +19,11 @@ import ( var CommonPlayer *Player type Player struct { - Playlist []string - MaxRunTime time.Duration - Stopper chan bool - - sampleRate beep.SampleRate + Playlist []string + MaxRunTime time.Duration + Stopper chan bool + currentCmd *exec.Cmd + currentCmdCh chan bool claironTime time.Duration claironFile string @@ -38,7 +31,7 @@ type Player struct { ntick int64 hasClaironed bool launched time.Time - volume *effects.Volume + volume uint16 dontUpdateVolume bool reverseOrder bool playedItem int @@ -66,11 +59,11 @@ func NewPlayer(cfg *config.Config) (*Player, error) { } p := Player{ - Stopper: make(chan bool, 1), - MaxRunTime: settings.MaxRunTime * time.Minute, - sampleRate: beep.SampleRate(cfg.SampleRate), - claironTime: settings.GongInterval * time.Minute, - claironFile: reveil.CurrentGongPath(cfg), + Stopper: make(chan bool, 1), + currentCmdCh: make(chan bool, 1), + MaxRunTime: settings.MaxRunTime * time.Minute, + claironTime: settings.GongInterval * time.Minute, + claironFile: reveil.CurrentGongPath(cfg), } // Load our track list @@ -100,95 +93,27 @@ func NewPlayer(cfg *config.Config) (*Player, error) { return &p, nil } -func loadFile(filepath string) (name string, s beep.StreamSeekCloser, format beep.Format, err error) { - var fd *os.File - - name = path.Base(filepath) - - fd, err = os.Open(filepath) - if err != nil { +func (p *Player) playFile(filepath string) (err error) { + p.currentCmd = exec.Command("paplay", filepath) + if err = p.currentCmd.Start(); err != nil { + log.Println("Running paplay err: ", err.Error()) + p.currentCmdCh <- true return } - switch strings.ToLower(path.Ext(filepath)) { - case ".flac": - s, format, err = flac.Decode(fd) - case ".mp3": - s, format, err = mp3.Decode(fd) - default: - s, format, err = wav.Decode(fd) - } + log.Println("Running paplay ", filepath) - if err != nil { - fd.Close() - return - } + err = p.currentCmd.Wait() + p.currentCmdCh <- true return } func (p *Player) WakeUp() { - log.Println("RUN WAKEUP FUNC") - log.Println("Playlist in use:", strings.Join(p.Playlist, " ; ")) - // Create infinite stream - stream := beep.Iterate(func() beep.Streamer { - if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { - log.Println("clairon time!") - p.claironTime += p.claironTime / 2 - _, sample, format, err := loadFile(p.claironFile) - if err == nil { - p.volume.Volume = 0.1 - p.dontUpdateVolume = true - if format.SampleRate != p.sampleRate { - return beep.Resample(3, format.SampleRate, p.sampleRate, sample) - } else { - return sample - } - } else { - log.Println("Error loading clairon:", err) - } - } - - p.dontUpdateVolume = false - p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 - - if p.reverseOrder { - p.playedItem -= 1 - } else { - p.playedItem += 1 - } - - if p.playedItem >= len(p.Playlist) { - p.playedItem = 0 - } else if p.playedItem < 0 { - p.playedItem = len(p.Playlist) - 1 - } - - // Load our current item - _, sample, format, err := loadFile(p.Playlist[p.playedItem]) - if err != nil { - log.Println("Error loading audio file %s: %s", p.Playlist[p.playedItem], err.Error()) - return nil - } - - // Resample if needed - log.Println("playing list item:", p.playedItem, "/", len(p.Playlist), ":", p.Playlist[p.playedItem]) - if format.SampleRate != p.sampleRate { - return beep.Resample(3, format.SampleRate, p.sampleRate, sample) - } else { - return sample - } - }) - // Prepare sound player - log.Println("Initializing sound player...") - speaker.Init(p.sampleRate, p.sampleRate.N(time.Second/10)) - defer speaker.Close() - - p.volume = &effects.Volume{stream, 10, -2, false} - speaker.Play(p.volume) + p.volume = 3500 ticker := time.NewTicker(time.Second) defer ticker.Stop() @@ -200,20 +125,53 @@ func (p *Player) WakeUp() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP) + p.currentCmdCh <- true loop: for { select { - case <-p.Stopper: - log.Println("Stopper activated") - break loop - case <-maxRun: - log.Println("Max run time exhausted") - break loop + case <-p.currentCmdCh: + if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { + log.Println("clairon time!") + p.claironTime += p.claironTime / 2 + p.SetVolume(65535) + p.dontUpdateVolume = true + go p.playFile(p.claironFile) + } else { + p.dontUpdateVolume = false + p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500) + p.SetVolume(p.volume) + + if p.reverseOrder { + p.playedItem -= 1 + } else { + p.playedItem += 1 + } + + if p.playedItem >= len(p.Playlist) { + p.playedItem = 0 + } else if p.playedItem < 0 { + p.playedItem = len(p.Playlist) - 1 + } + + log.Println("Next track: ", p.Playlist[p.playedItem]) + go p.playFile(p.Playlist[p.playedItem]) + } + case <-ticker.C: p.ntick += 1 if !p.dontUpdateVolume { - p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 + p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500) + p.SetVolume(p.volume) } + + case <-p.Stopper: + log.Println("Stopper activated") + break loop + + case <-maxRun: + log.Println("Max run time exhausted") + break loop + case <-interrupt: break loop } @@ -223,31 +181,47 @@ loop: // Calm down music loopcalm: - for i := 0; i < 2000; i += 1 { - p.volume.Volume -= 0.001 + for i := 0; i < 128 && p.volume >= 15000; i += 1 { + timer := time.NewTimer(40 * time.Millisecond) + + p.volume -= 256 + p.SetVolume(p.volume) - timer := time.NewTimer(4 * time.Millisecond) select { case <-p.Stopper: log.Println("Hard stop received...") timer.Stop() - p.volume.Volume = 0 + p.volume = 0 break loopcalm case <-timer.C: break } } + if p.currentCmd != nil && p.currentCmd.Process != nil { + p.currentCmd.Process.Kill() + } + + p.SetVolume(65535) + if p == CommonPlayer { log.Println("Destoying common player") CommonPlayer = nil - - // TODO: find a better way to deallocate the card - os.Exit(42) } } +func (p *Player) NextTrack() { + if p.currentCmd != nil && p.currentCmd.Process != nil { + p.currentCmd.Process.Kill() + } +} + +func (p *Player) SetVolume(volume uint16) error { + cmd := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d", volume)) + return cmd.Run() +} + func (p *Player) Stop() error { log.Println("Trying to stop the player") p.Stopper <- true