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
|
2020-06-24 12:05:30 +08:00
|
|
|
isSkipped chan bool
|
2020-06-23 18:42:18 +08:00
|
|
|
done chan bool
|
|
|
|
|
2020-06-21 23:47:02 +08:00
|
|
|
// to control the _volume internally
|
|
|
|
_volume *effects.Volume
|
2020-06-23 22:26:28 +08:00
|
|
|
ctrl *beep.Ctrl
|
2020-06-21 23:47:02 +08:00
|
|
|
volume float64
|
2020-06-23 18:42:18 +08:00
|
|
|
resampler *beep.Resampler
|
2020-06-21 23:47:02 +08:00
|
|
|
position time.Duration
|
|
|
|
length time.Duration
|
2020-07-10 23:30:28 +08:00
|
|
|
currentSong *AudioFile
|
2020-06-22 13:18:25 +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)
|
2020-07-24 17:06:18 +08:00
|
|
|
|
|
|
|
// making sure user does not give invalid volume
|
2020-08-02 16:36:47 +08:00
|
|
|
if volume > 100 || volume < 0 {
|
2020-07-24 17:06:18 +08:00
|
|
|
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 {
|
2020-07-10 23:30:28 +08:00
|
|
|
|
2020-07-11 12:44:43 +08:00
|
|
|
p.isSkipped = make(chan bool, 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
|
|
|
}
|
|
|
|
|
2020-06-20 23:00:19 +08:00
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
streamer, format, err := mp3.Decode(f)
|
2020-06-22 21:18:42 +08:00
|
|
|
|
2020-07-21 12:22:00 +08:00
|
|
|
if err != nil {
|
|
|
|
return tracerr.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer streamer.Close()
|
|
|
|
|
2020-06-21 23:47:02 +08:00
|
|
|
// song duration
|
|
|
|
p.length = format.SampleRate.D(streamer.Len())
|
|
|
|
|
|
|
|
if !p.hasInit {
|
2020-07-20 21:48:13 +08:00
|
|
|
|
|
|
|
err := speaker.
|
|
|
|
Init(format.SampleRate, format.SampleRate.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-07-11 12:44:43 +08:00
|
|
|
|
2020-06-21 23:47:02 +08:00
|
|
|
p.hasInit = true
|
|
|
|
}
|
2020-06-20 23:00:19 +08:00
|
|
|
|
|
|
|
p.format = &format
|
2020-07-11 12:44:43 +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-07-12 12:21:49 +08:00
|
|
|
timedPopup(" Current Song ", popupMessage, getPopupTimeout(), 0, 0)
|
2020-06-26 17:09:15 +08:00
|
|
|
|
2020-06-24 12:05:30 +08:00
|
|
|
done := make(chan bool, 1)
|
2020-06-23 18:42:18 +08:00
|
|
|
p.done = done
|
|
|
|
|
2020-06-20 23:00:19 +08:00
|
|
|
sstreamer := beep.Seq(streamer, beep.Callback(func() {
|
2020-06-24 12:05:30 +08:00
|
|
|
done <- true
|
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)
|
|
|
|
|
|
|
|
position := func() time.Duration {
|
|
|
|
return format.SampleRate.D(streamer.Position())
|
|
|
|
}
|
|
|
|
|
|
|
|
p.position = position()
|
2020-07-23 15:15:39 +08:00
|
|
|
p.isRunning = true
|
2020-06-20 23:00:19 +08:00
|
|
|
|
2020-07-12 12:21:57 +08:00
|
|
|
if p.isLoop {
|
2020-07-23 15:15:39 +08:00
|
|
|
gomu.queue.enqueue(currSong)
|
|
|
|
gomu.app.Draw()
|
2020-07-12 12:21:57 +08:00
|
|
|
}
|
|
|
|
|
2020-07-23 15:15:39 +08:00
|
|
|
gomu.playingBar.newProgress(currSong.name, int(p.length.Seconds()), 100)
|
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
|
|
|
|
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:
|
2020-06-21 23:47:02 +08:00
|
|
|
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
|
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()
|
2020-07-11 12:44:43 +08:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
break next
|
2020-06-23 18:42:18 +08:00
|
|
|
}
|
2020-07-11 12:44:43 +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-07-11 12:44:43 +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
|
|
|
|
}
|
|
|
|
|
|
|
|
i++
|
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()
|
|
|
|
p.position = position()
|
|
|
|
speaker.Unlock()
|
2020-06-25 14:12:02 +08:00
|
|
|
|
2020-07-23 15:15:39 +08:00
|
|
|
if i > gomu.playingBar.full {
|
2020-06-25 14:12:02 +08:00
|
|
|
break next
|
|
|
|
}
|
|
|
|
|
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.done <- true
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2020-07-24 17:06:18 +08:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|