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/api/alarms.go b/api/alarms.go index 59dfe98..e27771c 100644 --- a/api/alarms.go +++ b/api/alarms.go @@ -13,7 +13,7 @@ import ( func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), router *gin.RouterGroup) { router.GET("/alarms/next", func(c *gin.Context) { - alarm, err := reveil.GetNextAlarm(db) + alarm, err := reveil.GetNextAlarm(cfg, db) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return @@ -200,8 +200,8 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTim repeatedAlarmsRoutes.GET("", func(c *gin.Context) { alarm := c.MustGet("alarm").(*reveil.AlarmRepeated) - alarm.FillExcepts(db) - alarm.NextTime = alarm.GetNextOccurence(db) + alarm.FillExcepts(cfg, db) + alarm.NextTime = alarm.GetNextOccurence(cfg, db) c.JSON(http.StatusOK, alarm) }) diff --git a/app.go b/app.go index 778abd6..0867a7e 100644 --- a/app.go +++ b/app.go @@ -64,6 +64,7 @@ func (app *App) Start() { Handler: app.router, } + log.Println("Current timezone:", app.cfg.Timezone.String()) app.ResetTimer() log.Printf("Ready, listening on %s\n", app.cfg.Bind) @@ -78,7 +79,7 @@ func (app *App) ResetTimer() { app.nextAlarm = nil } - if na, err := reveil.GetNextAlarm(app.db); err == nil && na != nil { + if na, err := reveil.GetNextAlarm(app.cfg, app.db); err == nil && na != nil { app.nextAlarm = time.AfterFunc(time.Until(*na), func() { app.nextAlarm = nil reveil.RemoveOldAlarmsSingle(app.db) diff --git a/config/cli.go b/config/cli.go index 1894daa..4677a69 100644 --- a/config/cli.go +++ b/config/cli.go @@ -17,6 +17,7 @@ func (c *Config) declareFlags() { flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions") flag.StringVar(&c.RoutinesDir, "routines-dir", c.RoutinesDir, "Path to the directory containing the routines") flag.IntVar(&c.SampleRate, "samplerate", c.SampleRate, "Samplerate for unifying output stream") + flag.Var(&c.Timezone, "timezone", "Timezone to use when dealing with times") // Others flags are declared in some other files when they need specials configurations } diff --git a/config/config.go b/config/config.go index 26fe5c1..26f791a 100644 --- a/config/config.go +++ b/config/config.go @@ -18,6 +18,7 @@ type Config struct { ActionsDir string RoutinesDir string + Timezone Timezone SampleRate int } diff --git a/config/custom.go b/config/custom.go index 716a2f7..be0437b 100644 --- a/config/custom.go +++ b/config/custom.go @@ -3,6 +3,7 @@ package config import ( "encoding/base64" "net/url" + "time" ) type JWTSecretKey []byte @@ -42,3 +43,33 @@ func (i *URL) Set(value string) error { i.URL = u return nil } + +type Timezone struct { + tz *time.Location +} + +func (tz *Timezone) GetLocation() *time.Location { + if tz.tz != nil { + return tz.tz + } else { + return time.Local + } +} + +func (tz *Timezone) String() string { + if tz.tz != nil { + return tz.tz.String() + } else { + return time.Local.String() + } +} + +func (tz *Timezone) Set(value string) error { + newtz, err := time.LoadLocation(value) + if err != nil { + return err + } + + tz.tz = newtz + return nil +} diff --git a/config/env.go b/config/env.go index ae852a3..adac3ea 100644 --- a/config/env.go +++ b/config/env.go @@ -7,10 +7,10 @@ import ( ) // FromEnv analyzes all the environment variables to find each one -// starting by GUSTUS_ +// starting by REVEIL_ func (c *Config) FromEnv() error { for _, line := range os.Environ() { - if strings.HasPrefix(line, "GUSTUS_") { + if strings.HasPrefix(line, "REVEIL_") { err := c.parseLine(line) if err != nil { return fmt.Errorf("error in environment (%q): %w", line, err) diff --git a/go.mod b/go.mod index 23551b2..d3afff1 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,12 @@ module git.nemunai.re/nemunaire/reveil go 1.18 require ( - github.com/faiface/beep v0.0.0-00010101000000-000000000000 + github.com/faiface/beep v1.1.0 github.com/gin-gonic/gin v1.8.1 github.com/syndtr/goleveldb v1.0.0 ) require ( - github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect @@ -17,7 +16,7 @@ require ( github.com/goccy/go-json v0.9.7 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/hajimehoshi/go-mp3 v0.3.0 // indirect - github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 // indirect + github.com/hajimehoshi/oto v0.7.1 // indirect github.com/icza/bitio v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -30,11 +29,12 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/ugorji/go/codec v1.2.7 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect + golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect + golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) - -replace github.com/faiface/beep => github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755 diff --git a/go.sum b/go.sum index 9d932f0..56c0f5f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,11 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755 h1:rkuKNEd+Izze/hA44R1kzPs9BXa544xXeufys8x0fkc= -github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755/go.mod h1:PWWzyIlbyHQjQ/gJzGiMyDAvjo/t5L8TC8qkyX8UfWs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 h1:tmSauY5l3s/Cp5n+cEiG1epUR2AejmdHeMJMycMFxb0= -github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= +github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= +github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= @@ -39,8 +37,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 h1:m29xzbn3Pv5MgvgjMPs7m28uhUgVt3B3AIGjQLgkqUI= -github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4/go.mod h1:OdGUICBjy7upAjvqqacbB63XIuYR3fqXZ7kYtlVYJgQ= +github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= +github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8= @@ -103,9 +101,12 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/model/alarm.go b/model/alarm.go index e6a8bbd..12920b9 100644 --- a/model/alarm.go +++ b/model/alarm.go @@ -4,6 +4,8 @@ import ( "fmt" "sort" "time" + + "git.nemunai.re/nemunaire/reveil/config" ) type Date time.Time @@ -38,7 +40,7 @@ func (h *Hour) UnmarshalJSON(src []byte) error { return nil } -func GetNextAlarm(db *LevelDBStorage) (*time.Time, error) { +func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, error) { alarmsRepeated, err := GetAlarmsRepeated(db) if err != nil { return nil, err @@ -46,7 +48,7 @@ func GetNextAlarm(db *LevelDBStorage) (*time.Time, error) { var closestAlarm *time.Time for _, alarm := range alarmsRepeated { - next := alarm.GetNextOccurence(db) + next := alarm.GetNextOccurence(cfg, db) if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) { closestAlarm = next } @@ -92,7 +94,7 @@ type AlarmRepeated struct { NextTime *time.Time `json:"next_time,omitempty"` } -func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error { +func (a *AlarmRepeated) FillExcepts(cfg *config.Config, db *LevelDBStorage) error { if a.IgnoreExceptions { return nil } @@ -112,7 +114,7 @@ func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error { end := time.Time(*exception.End).AddDate(0, 0, 1) for t := time.Time(*exception.Start); end.After(t); t = t.AddDate(0, 0, 1) { if t.Weekday() == a.Weekday { - a.Excepts = append(a.Excepts, time.Date(t.Year(), t.Month(), t.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location())) + a.Excepts = append(a.Excepts, time.Date(t.Year(), t.Month(), t.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, cfg.Timezone.GetLocation())) t.AddDate(0, 0, 6) } } @@ -123,14 +125,14 @@ func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error { return nil } -func (a *AlarmRepeated) GetNextOccurence(db *LevelDBStorage) *time.Time { +func (a *AlarmRepeated) GetNextOccurence(cfg *config.Config, db *LevelDBStorage) *time.Time { if len(a.Excepts) == 0 { - a.FillExcepts(db) + a.FillExcepts(cfg, db) } now := time.Now() - today := time.Date(now.Year(), now.Month(), now.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location()) + today := time.Date(now.Year(), now.Month(), now.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, cfg.Timezone.GetLocation()) if now.After(today) { today = today.AddDate(0, 0, 1) } diff --git a/player/player.go b/player/player.go index 2162fd1..d543c77 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 @@ -49,6 +42,10 @@ func WakeUp(cfg *config.Config) (err error) { return fmt.Errorf("Unable to start the player: a player is already running") } + seed := time.Now().Unix() + seed -= seed % 172800 + rand.Seed(seed) + CommonPlayer, err = NewPlayer(cfg) if err != nil { return err @@ -66,11 +63,12 @@ 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), + reverseOrder: int(time.Now().Unix()/86400)%2 == 0, } // Load our track list @@ -100,95 +98,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 +130,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 +186,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 diff --git a/ui/src/lib/Toaster.svelte b/ui/src/lib/Toaster.svelte new file mode 100644 index 0000000..a8d7345 --- /dev/null +++ b/ui/src/lib/Toaster.svelte @@ -0,0 +1,22 @@ + + +