diff --git a/player.go b/player.go index 14e6278..dabfb0c 100644 --- a/player.go +++ b/player.go @@ -9,121 +9,158 @@ import ( "time" "github.com/faiface/beep" + "github.com/faiface/beep/effects" "github.com/faiface/beep/mp3" "github.com/faiface/beep/speaker" + "github.com/rivo/tview" ) type Song struct { name string path string position string - format string } +type handler func(list *tview.List, tree *tview.TreeView, playingBar *tview.Box) + type Player struct { - list []string - IsRunning bool - hasInit bool - ctrl *beep.Ctrl - current string - format *beep.Format - position string + queue []string + IsRunning bool + hasInit bool + current string + format *beep.Format + // to control the _volume internally + _volume *effects.Volume + volume float64 + position time.Duration + length time.Duration currentSong Song -} -func Init(paths []string) (*Player, error) { - - p := &Player{list: paths, IsRunning: false, hasInit: false} - - if len(paths) == 0 { - return nil, errors.New("Cannot play with empty list") - } - - return p, nil + // to access sections + list *tview.List + tree *tview.TreeView + playingBar *Progress + app *tview.Application + afterPlayHandler handler + beforePlayHandler handler } func (p *Player) Push(song string) { - p.list = append(p.list, song) + p.queue = append(p.queue, song) } func (p *Player) Pop() (string, error) { - if len(p.list) == 0 { + if len(p.queue) == 0 { return "", errors.New("Empty list") } - a := p.list[0] - p.list = p.list[1:] + a := p.queue[0] + p.queue = p.queue[1:] p.current = a return a, nil } -func (p *Player) Run() error { +func (p *Player) Run() { first, err := p.Pop() + // removes playing song from the queue + p.list.RemoveItem(0) + p.app.Draw() + if err != nil { p.IsRunning = false - return err + log(err.Error()) } f, err := os.Open(first) - fformat, err := GetFileContentType(f) - - if err != nil { - return err - } - - song := &Song{name: GetName(f.Name()), path: first, format: fformat} - p.currentSong = *song - - if err != nil { - return err - } - defer f.Close() streamer, format, err := mp3.Decode(f) - - p.format = &format - - if err != nil { - return err - } - - defer streamer.Close() + + // song duration + p.length = format.SampleRate.D(streamer.Len()) if !p.hasInit { speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) p.hasInit = true } + p.format = &format + + if err != nil { + log(err.Error()) + } + + defer streamer.Close() + + if err != nil { + log(err.Error()) + } + + song := &Song{name: GetName(f.Name()), path: first} + p.currentSong = *song + done := make(chan bool) sstreamer := beep.Seq(streamer, beep.Callback(func() { done <- true - close(done) })) - p.ctrl = &beep.Ctrl{Streamer: sstreamer, Paused: false} + ctrl := &beep.Ctrl{Streamer: sstreamer, Paused: false} - speaker.Play(p.ctrl) - position := func() string { - return format.SampleRate.D(streamer.Position()).Round(time.Second).String() + volume := &effects.Volume{ + Streamer: ctrl, + Base: 2, + Volume: 0, + Silent: false, + } + + // 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() - p.IsRunning = true + p.playingBar.NewProgress(song.name, int(p.length.Seconds()), 100) + p.playingBar.Run() + + go func () { + + i := 0 + + for { + i++ + p.playingBar.progress <- 1 + + if i > p.playingBar.full { + break + } + + time.Sleep(time.Second) + } + + }() + for { select { case <-done: - p.position = "" + close(done) + p.playingBar._progress = 0 + p.position = 0 p.current = "" - if len(p.list) != 0 { + if len(p.queue) != 0 { go p.Run() } else { p.IsRunning = false @@ -139,20 +176,19 @@ func (p *Player) Run() error { next: - return nil } func (p *Player) Pause() { speaker.Lock() - p.ctrl.Paused = true + p._volume.Streamer.(*beep.Ctrl).Paused = true p.IsRunning = false speaker.Unlock() } func (p *Player) Play() { speaker.Lock() - p.ctrl.Paused = false + p._volume.Streamer.(*beep.Ctrl).Paused = false p.IsRunning = true speaker.Unlock() } @@ -161,7 +197,6 @@ func (p *Player) CurrentSong() Song { return p.currentSong } - func GetFileContentType(out *os.File) (string, error) { buffer := make([]byte, 512) @@ -177,5 +212,38 @@ func GetFileContentType(out *os.File) (string, error) { } func GetName(fn string) string { - return strings.TrimSuffix(fn, path.Ext(fn)) + return strings.TrimSuffix(path.Base(fn), path.Ext(fn)) +} + +// volume up and volume down using -0.5 or +0.5 +func (p *Player) Volume(v float64) { + speaker.Lock() + p._volume.Volume += v + p.volume = p._volume.Volume + speaker.Unlock() +} + +func (p *Player) TogglePause() { + + if p._volume.Streamer.(*beep.Ctrl).Paused { + p.Play() + } else { + p.Pause() + } +} + +func (p *Player) BeforePlayHook(handler func( + list *tview.List, + tree *tview.TreeView, + playingBar *tview.Box), +) { + p.beforePlayHandler = handler +} + +func (p *Player) AfterPlayHook(handler func( + list *tview.List, + tree *tview.TreeView, + playingBar *tview.Box), +) { + p.afterPlayHandler = handler } diff --git a/playingbar.go b/playingbar.go index fef3957..0d047d7 100644 --- a/playingbar.go +++ b/playingbar.go @@ -1,8 +1,106 @@ package main -import "github.com/rivo/tview" +import ( + "fmt" + "strconv" + "strings" + "time" -func NowPlayingBar() *tview.Box { - return tview.NewBox().SetBorder(true). - SetTitle("Currently Playing") + "github.com/gdamore/tcell" + "github.com/rivo/tview" +) + +func PlayingBar(app *tview.Application) *Progress { + + textView := tview.NewTextView(). + SetChangedFunc(func () { + app.Draw() + }) + + progress := InitProgressBar(textView) + + + return progress } + + +type Progress struct { + textView *tview.TextView + full int + limit int + progress chan int + frame *tview.Frame + _progress int +} + + + +// full is the maximum amount of value can be sent to channel +// limit is the progress bar size +func InitProgressBar(txt *tview.TextView) *Progress { + p := &Progress{textView: txt} + p.progress = make(chan int) + p.textView.SetTextAlign(tview.AlignCenter) + + p.frame = tview.NewFrame(p.textView).SetBorders(1, 1, 1, 1, 1, 1) + p.frame.SetBorder(true).SetTitle("Now Playing") + p.SetSongTitle("-") + + p.textView.SetText(fmt.Sprintf("%s %s %s", "0s", strings.Repeat("□", 100), "0s")) + + return p +} + + +func (p *Progress) Run() { + + go func() { // Simple channel status gauge (progress bar) + for { + p._progress += <-p.progress + + p.textView.Clear() + + if p._progress > p.full { + p._progress = 0 + break + } + + start, err := time.ParseDuration(strconv.Itoa(p._progress) + "s") + + if err != nil { + panic(err) + } + + end, err := time.ParseDuration(strconv.Itoa(p.full) + "s") + + if err != nil { + panic(err) + } + + x := p._progress * p.limit / p.full + p.textView.SetText(fmt.Sprintf("%s %s%s %s", + start.String(), + strings.Repeat("■", x), + strings.Repeat("□", p.limit-x), + end.String(), + )) + + } + }() +} + +func (p *Progress) SetSongTitle(title string) { + + p.frame.Clear() + p.frame.AddText(title, true, tview.AlignCenter, tcell.ColorGreen) + +} + +func (p *Progress) NewProgress(songTitle string, full, limit int) { + p.full = full + p.limit = limit + p._progress = 0 + p.SetSongTitle(songTitle) +} + +