package playback import ( "io" "sync" "time" "github.com/delucks/go-subsonic" "github.com/gopxl/beep" "github.com/gopxl/beep/mp3" "github.com/gopxl/beep/speaker" ) type Controller struct { stream beep.StreamSeekCloser song *subsonic.Child songElapsedFunc func(elapsed time.Duration) format *beep.Format closeChan chan bool songEndedFunc func(song *subsonic.Child) isPlaying bool ctrl *beep.Ctrl tickerRunning bool controllerLock *sync.Mutex } func NewController() *Controller { controller := &Controller{ closeChan: make(chan bool), controllerLock: &sync.Mutex{}, } return controller } func (c *Controller) Playing() bool { return c.isPlaying } func (c *Controller) SetSongElapsedFunc(f func(elapsed time.Duration)) { c.songElapsedFunc = f } func (c *Controller) SetSongEndedFunc(f func(song *subsonic.Child)) { c.songEndedFunc = f } func (c *Controller) Close() error { if c.tickerRunning { c.closeChan <- true } if c.stream != nil { c.Stop() speaker.Close() } return nil } func (c *Controller) TogglePlayPause() { if c.ctrl == nil { if c.stream != nil { c.ctrl.Streamer = c.stream c.ctrl.Paused = false return } return } c.ctrl.Paused = !c.ctrl.Paused } func (c *Controller) Stop() { if c.ctrl == nil { return } go func() { c.controllerLock.Lock() speaker.Clear() c.ctrl.Paused = true c.isPlaying = false c.ctrl = nil c.stream = nil c.song = nil c.songElapsedFunc(time.Duration(0)) c.controllerLock.Unlock() }() } func (c *Controller) playbackTicker(format beep.Format) { if c.tickerRunning { return } c.tickerRunning = true for { c.controllerLock.Lock() select { case <-c.closeChan: c.tickerRunning = false c.controllerLock.Unlock() return default: if c.isPlaying && c.stream != nil { pos := c.stream.Position() songDuration := time.Duration(c.song.Duration) * time.Second elapsed := format.SampleRate.D(pos).Round(time.Second) if elapsed >= songDuration { elapsed = songDuration c.songElapsedFunc(elapsed) c.tickerRunning = false c.controllerLock.Unlock() c.songEndedFunc(c.song) return } c.songElapsedFunc(elapsed) } } c.controllerLock.Unlock() time.Sleep(time.Second) } } func (c *Controller) Stream(reader io.Reader, song *subsonic.Child) { c.Stop() readerCloser := io.NopCloser(reader) decodedMp3, format, err := mp3.Decode(readerCloser) decodedMp3.Position() if err != nil { panic("mp3.NewDecoder failed: " + err.Error()) } if c.format == nil { c.format = &format } var stream beep.Streamer = decodedMp3 if err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)); err != nil { stream = beep.Resample(3, format.SampleRate, c.format.SampleRate, decodedMp3) } c.stream = decodedMp3 c.song = song ctrl := &beep.Ctrl{Streamer: stream} speaker.Play(beep.Seq(ctrl)) c.ctrl = ctrl c.isPlaying = true if !c.tickerRunning { go c.playbackTicker(format) } }