gomu/player.go

329 lines
5.8 KiB
Go
Raw Normal View History

2020-06-22 00:05:56 +08:00
// Copyright (C) 2020 Raziman
2020-06-20 23:00:19 +08:00
package main
import (
2020-06-26 17:09:15 +08:00
"fmt"
2020-06-20 23:00:19 +08:00
"os"
"time"
"github.com/faiface/beep"
2020-06-21 23:47:02 +08:00
"github.com/faiface/beep/effects"
2020-06-20 23:00:19 +08:00
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
2020-07-11 12:47:28 +08:00
"github.com/spf13/viper"
2020-07-21 12:22:00 +08:00
"github.com/ztrue/tracerr"
2020-06-20 23:00:19 +08:00
)
type Player struct {
2020-06-21 23:47:02 +08:00
hasInit bool
format *beep.Format
2020-07-12 12:21:57 +08:00
isLoop bool
2020-07-23 15:15:39 +08:00
isRunning bool
isSkipped chan struct{}
done chan struct{}
2020-06-23 18:42:18 +08:00
2020-06-21 23:47:02 +08:00
// to control the _volume internally
2021-02-03 10:50:01 +08:00
_volume *effects.Volume
ctrl *beep.Ctrl
volume float64
resampler *beep.Resampler
position time.Duration
length time.Duration
currentSong *AudioFile
streamSeekCloser beep.StreamSeekCloser
2021-01-27 01:10:48 +08:00
// is used to send progress
2021-02-01 13:48:34 +08:00
i int
2021-02-03 10:50:01 +08:00
}
2021-01-27 01:10:48 +08:00
2020-07-23 15:15:39 +08:00
func newPlayer() *Player {
2020-08-02 16:36:47 +08:00
volume := viper.GetInt("general.volume")
2020-07-11 12:47:28 +08:00
// Read initial volume from config
2020-08-02 16:36:47 +08:00
initVol := absVolume(volume)
// making sure user does not give invalid volume
2020-08-02 16:36:47 +08:00
if volume > 100 || volume < 0 {
initVol = 0
}
2020-06-20 23:00:19 +08:00
2020-07-11 12:47:28 +08:00
return &Player{volume: initVol}
}
2020-06-21 23:47:02 +08:00
2020-07-23 15:15:39 +08:00
func (p *Player) run(currSong *AudioFile) error {
2021-02-03 10:50:01 +08:00
p.isSkipped = make(chan struct{}, 1)
2020-07-23 15:15:39 +08:00
f, err := os.Open(currSong.path)
2020-06-20 23:00:19 +08:00
2020-07-06 17:02:59 +08:00
if err != nil {
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-06 17:02:59 +08:00
}
2021-02-03 10:50:01 +08:00
defer f.Close()
stream, format, err := mp3.Decode(f)
2020-06-20 23:00:19 +08:00
2021-02-03 10:50:01 +08:00
p.streamSeekCloser = stream
p.format = &format
2020-06-22 21:18:42 +08:00
2020-07-21 12:22:00 +08:00
if err != nil {
return tracerr.Wrap(err)
}
2021-02-03 10:50:01 +08:00
defer stream.Close()
2021-02-01 13:48:34 +08:00
2020-06-21 23:47:02 +08:00
// song duration
2021-02-03 10:50:01 +08:00
p.length = p.format.SampleRate.D(p.streamSeekCloser.Len())
2020-06-21 23:47:02 +08:00
2021-02-16 02:25:01 +08:00
sr := beep.SampleRate(48000)
2020-06-21 23:47:02 +08:00
if !p.hasInit {
2020-07-20 21:48:13 +08:00
err := speaker.
2021-02-16 02:25:01 +08:00
Init(sr, sr.N(time.Second/10))
2020-07-03 00:50:32 +08:00
if err != nil {
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-03 00:50:32 +08:00
}
2020-06-21 23:47:02 +08:00
p.hasInit = true
}
2020-06-20 23:00:19 +08:00
p.currentSong = currSong
2020-06-21 23:47:02 +08:00
2020-07-21 01:21:59 +08:00
popupMessage := fmt.Sprintf("%s\n\n[ %s ]",
2020-07-23 15:15:39 +08:00
currSong.name, fmtDuration(p.length))
2020-06-26 17:09:15 +08:00
2020-08-25 20:45:16 +08:00
defaultTimedPopup(" Current Song ", popupMessage)
2020-06-26 17:09:15 +08:00
2021-02-16 02:25:01 +08:00
resampled := beep.Resample(4, p.format.SampleRate, sr, p.streamSeekCloser)
done := make(chan struct{}, 1)
2020-06-23 18:42:18 +08:00
p.done = done
2021-02-16 02:25:01 +08:00
sstreamer := beep.Seq(resampled, beep.Callback(func() {
done <- struct{}{}
2020-06-20 23:00:19 +08:00
}))
2020-07-20 21:48:13 +08:00
ctrl := &beep.Ctrl{
2020-07-21 01:21:59 +08:00
Streamer: sstreamer,
Paused: false,
2020-07-20 21:48:13 +08:00
}
2020-06-23 22:26:28 +08:00
p.ctrl = ctrl
2020-06-20 23:00:19 +08:00
2020-06-23 18:42:18 +08:00
resampler := beep.ResampleRatio(4, 1, ctrl)
p.resampler = resampler
2020-06-21 23:47:02 +08:00
volume := &effects.Volume{
2020-06-23 18:42:18 +08:00
Streamer: resampler,
2020-06-21 23:47:02 +08:00
Base: 2,
Volume: 0,
Silent: false,
2020-06-20 23:00:19 +08:00
}
2020-06-21 23:47:02 +08:00
// sets the volume of previous player
volume.Volume += p.volume
p._volume = volume
speaker.Play(p._volume)
2021-02-03 10:50:01 +08:00
p.position = p.getPosition()
2020-07-23 15:15:39 +08:00
p.isRunning = true
2020-06-20 23:00:19 +08:00
2020-08-22 14:51:16 +08:00
gomu.playingBar.newProgress(currSong.name, int(p.length.Seconds()))
2020-07-21 12:22:00 +08:00
go func() {
2020-07-23 15:15:39 +08:00
if err := gomu.playingBar.run(); err != nil {
logError(err)
2020-07-21 12:22:00 +08:00
}
}()
2020-06-21 23:47:02 +08:00
2020-06-25 14:12:02 +08:00
// is used to send progress
2021-02-03 10:50:01 +08:00
p.i = 0
2020-06-21 23:47:02 +08:00
2020-06-23 18:42:18 +08:00
next:
2020-06-20 23:00:19 +08:00
for {
2020-06-25 14:12:02 +08:00
2020-06-20 23:00:19 +08:00
select {
case <-done:
2021-02-01 13:48:34 +08:00
if p.isLoop {
gomu.queue.enqueue(currSong)
gomu.app.Draw()
}
// close(done)
2020-06-23 20:15:29 +08:00
p.position = 0
2020-07-23 15:15:39 +08:00
p.isRunning = false
2020-06-23 20:15:29 +08:00
p.format = nil
2021-02-16 01:32:05 +08:00
// p.hasInit = false
2020-07-23 15:15:39 +08:00
gomu.playingBar.stop()
2020-06-22 21:18:42 +08:00
2020-07-23 15:15:39 +08:00
nextSong, err := gomu.queue.dequeue()
gomu.app.Draw()
if err != nil {
// when there are no songs to be played, set currentSong as nil
p.currentSong = nil
gomu.playingBar.setDefault()
break next
2020-06-23 18:42:18 +08:00
}
2020-07-21 12:22:00 +08:00
go func() {
2020-07-23 15:15:39 +08:00
if err := p.run(nextSong); err != nil {
logError(err)
2020-07-21 12:22:00 +08:00
}
}()
2020-06-23 18:42:18 +08:00
break next
2020-06-25 14:12:02 +08:00
2020-06-20 23:00:19 +08:00
case <-time.After(time.Second):
2020-06-25 14:12:02 +08:00
// stop progress bar from progressing when paused
2020-07-23 15:15:39 +08:00
if !p.isRunning {
2020-06-25 14:12:02 +08:00
continue
}
2021-02-03 10:50:01 +08:00
p.i++
2021-02-16 01:32:05 +08:00
if p.i >= gomu.playingBar.full {
done <- struct{}{}
continue
// break next
}
2020-07-23 15:15:39 +08:00
gomu.playingBar.progress <- 1
2020-06-25 14:12:02 +08:00
2020-06-20 23:00:19 +08:00
speaker.Lock()
2021-02-03 10:50:01 +08:00
p.position = p.getPosition()
2020-06-20 23:00:19 +08:00
speaker.Unlock()
2020-06-25 14:12:02 +08:00
2021-02-16 01:32:05 +08:00
/* if p.i > gomu.playingBar.full {
2020-06-25 14:12:02 +08:00
break next
2021-02-16 01:32:05 +08:00
} */
2020-06-25 14:12:02 +08:00
2020-06-20 23:00:19 +08:00
}
2020-06-25 14:12:02 +08:00
2020-06-20 23:00:19 +08:00
}
2020-07-21 12:22:00 +08:00
return nil
2020-06-20 23:00:19 +08:00
}
2020-07-23 15:15:39 +08:00
func (p *Player) pause() {
2020-06-20 23:00:19 +08:00
speaker.Lock()
2020-06-23 22:26:28 +08:00
p.ctrl.Paused = true
2020-07-23 15:15:39 +08:00
p.isRunning = false
2020-06-20 23:00:19 +08:00
speaker.Unlock()
}
2020-07-23 15:15:39 +08:00
func (p *Player) play() {
2020-06-20 23:00:19 +08:00
speaker.Lock()
2020-06-23 22:26:28 +08:00
p.ctrl.Paused = false
2020-07-23 15:15:39 +08:00
p.isRunning = true
2020-06-20 23:00:19 +08:00
speaker.Unlock()
}
2020-06-21 23:47:02 +08:00
// volume up and volume down using -0.5 or +0.5
2020-07-23 15:15:39 +08:00
func (p *Player) setVolume(v float64) float64 {
2020-06-26 17:09:15 +08:00
2020-07-27 22:46:40 +08:00
defer func() {
// saves the volume
volume := volToHuman(p.volume)
viper.Set("general.volume", volume)
}()
2020-07-12 09:48:48 +08:00
// check if no songs playing currently
2020-06-26 17:09:15 +08:00
if p._volume == nil {
p.volume += v
2020-07-04 10:17:52 +08:00
return p.volume
2020-06-26 17:09:15 +08:00
}
2020-06-21 23:47:02 +08:00
speaker.Lock()
p._volume.Volume += v
p.volume = p._volume.Volume
speaker.Unlock()
2020-06-26 17:09:15 +08:00
return p.volume
2020-06-21 23:47:02 +08:00
}
2020-07-23 15:15:39 +08:00
func (p *Player) togglePause() {
2020-07-22 21:08:55 +08:00
if p.ctrl == nil {
return
}
2020-06-23 22:26:28 +08:00
if p.ctrl.Paused {
2020-07-23 15:15:39 +08:00
p.play()
2020-06-21 23:47:02 +08:00
} else {
2020-07-23 15:15:39 +08:00
p.pause()
2020-06-21 23:47:02 +08:00
}
}
2020-06-23 18:42:18 +08:00
// skips current song
2020-07-23 15:15:39 +08:00
func (p *Player) skip() {
2020-07-01 17:48:33 +08:00
2020-07-23 15:15:39 +08:00
if gomu.queue.GetItemCount() < 1 {
2020-07-12 12:21:57 +08:00
return
2020-06-23 18:42:18 +08:00
}
2020-07-12 12:21:57 +08:00
p.ctrl.Streamer = nil
p.streamSeekCloser.Close()
p.done <- struct{}{}
2020-07-12 12:21:57 +08:00
}
// Toggles the queue to loop
// dequeued item will be enqueued back
// function returns loop state
2020-07-23 15:15:39 +08:00
func (p *Player) toggleLoop() bool {
2020-07-12 12:21:57 +08:00
p.isLoop = !p.isLoop
return p.isLoop
2020-06-23 18:42:18 +08:00
}
2020-07-02 20:47:20 +08:00
2020-07-23 15:34:56 +08:00
// Gets the length of the song in the queue
2020-07-23 15:15:39 +08:00
func getLength(audioPath string) (time.Duration, error) {
2020-07-02 20:47:20 +08:00
f, err := os.Open(audioPath)
if err != nil {
2020-07-21 12:22:00 +08:00
return 0, tracerr.Wrap(err)
2020-07-02 20:47:20 +08:00
}
2020-07-06 17:02:59 +08:00
defer f.Close()
2020-07-02 20:47:20 +08:00
streamer, format, err := mp3.Decode(f)
if err != nil {
2020-07-21 12:22:00 +08:00
return 0, tracerr.Wrap(err)
2020-07-02 20:47:20 +08:00
}
2020-07-06 17:02:59 +08:00
defer streamer.Close()
2020-07-02 20:47:20 +08:00
return format.SampleRate.D(streamer.Len()), nil
}
// volToHuman converts float64 volume that is used by audio library to human
// readable form (0 - 100)
func volToHuman(volume float64) int {
return int(volume*10) + 100
}
// absVolume converts human readable form volume (0 - 100) to float64 volume
// that is used by the audio library
func absVolume(volume int) float64 {
return (float64(volume) - 100) / 10
}
2021-01-27 01:10:48 +08:00
2021-02-03 10:50:01 +08:00
func (p *Player) getPosition() time.Duration {
return p.format.SampleRate.D(p.streamSeekCloser.Position())
2021-01-27 01:10:48 +08:00
}
//seek is the function to move forward and rewind
func (p *Player) seek(pos int) error {
2021-02-01 13:48:34 +08:00
speaker.Lock()
defer speaker.Unlock()
2021-02-03 10:50:01 +08:00
err := p.streamSeekCloser.Seek(pos * int(p.format.SampleRate))
2021-02-16 01:32:05 +08:00
p.i = pos
2021-02-01 13:48:34 +08:00
return err
2021-01-27 01:10:48 +08:00
}
2021-02-13 21:48:46 +08:00
// isPaused is used to distinguish the player between pause and stop
func (p *Player) isPaused() bool {
if p.ctrl == nil {
return false
}
return p.ctrl.Paused
}