Fix data race in playingbar.go and player.go

This commit is contained in:
tramhao 2021-03-30 13:22:21 +08:00
parent 5dacd77cd1
commit 5d46449ddd
8 changed files with 151 additions and 93 deletions

View File

@ -307,64 +307,70 @@ func (c Command) defineCommands() {
c.define("forward", func() {
if gomu.player.IsRunning() && !gomu.player.IsPaused() {
position := gomu.playingBar.progress + 10
if position < gomu.playingBar.full {
position := gomu.playingBar.getProgress() + 10
if position < gomu.playingBar.getFull() {
err := gomu.player.Seek(position)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = position
gomu.playingBar.setProgress(position)
}
}
})
c.define("rewind", func() {
if gomu.player.IsRunning() && !gomu.player.IsPaused() {
position := gomu.playingBar.progress - 10
position := gomu.playingBar.getProgress() - 10
if position-1 > 0 {
err := gomu.player.Seek(position)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = position
gomu.playingBar.setProgress(position)
} else {
err := gomu.player.Seek(0)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = 0
gomu.playingBar.setProgress(0)
}
}
})
c.define("forward_fast", func() {
if gomu.player.IsRunning() && !gomu.player.IsPaused() {
position := gomu.playingBar.progress + 60
if position < gomu.playingBar.full {
position := gomu.playingBar.getProgress() + 60
if position < gomu.playingBar.getFull() {
err := gomu.player.Seek(position)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = position
gomu.playingBar.setProgress(position)
}
}
})
c.define("rewind_fast", func() {
if gomu.player.IsRunning() && !gomu.player.IsPaused() {
position := gomu.playingBar.progress - 60
position := gomu.playingBar.getProgress() - 60
if position-1 > 0 {
err := gomu.player.Seek(position)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = position
gomu.playingBar.setProgress(position)
} else {
err := gomu.player.Seek(0)
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
gomu.playingBar.progress = 0
gomu.playingBar.setProgress(0)
}
}
})
@ -372,14 +378,16 @@ func (c Command) defineCommands() {
c.define("yank", func() {
err := gomu.playlist.yank()
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
})
c.define("paste", func() {
err := gomu.playlist.paste()
if err != nil {
logError(err)
errorPopup(err)
gomu.app.Draw()
}
})

View File

@ -1,10 +1,11 @@
// Package hook is handling event hookds
package hook
type EventHook struct {
events map[string][]func()
}
// NewNewEventHook returns new instance of EventHook
// NewEventHook returns new instance of EventHook
func NewEventHook() *EventHook {
return &EventHook{make(map[string][]func())}
}

View File

@ -1,7 +1,9 @@
// Package player is the place actually play the music
package player
import (
"os"
"sync"
"time"
"github.com/faiface/beep"
@ -31,6 +33,7 @@ type Player struct {
songFinish func(Audio)
songStart func(Audio)
songSkip func(Audio)
mu sync.Mutex
}
// New returns new Player instance.
@ -100,15 +103,16 @@ func (p *Player) Run(currSong Audio) error {
}
p.streamSeekCloser = stream
p.format = &format
// song duration
p.length = p.format.SampleRate.D(p.streamSeekCloser.Len())
p.length = format.SampleRate.D(p.streamSeekCloser.Len())
sr := beep.SampleRate(48000)
if !p.hasInit {
// p.mu.Lock()
err := speaker.Init(sr, sr.N(time.Second/10))
// p.mu.Unlock()
if err != nil {
return tracerr.Wrap(err)
@ -120,7 +124,7 @@ func (p *Player) Run(currSong Audio) error {
p.currentSong = currSong
// resample to adapt to sample rate of new songs
resampled := beep.Resample(4, p.format.SampleRate, sr, p.streamSeekCloser)
resampled := beep.Resample(4, format.SampleRate, sr, p.streamSeekCloser)
sstreamer := beep.Seq(resampled, beep.Callback(func() {
p.isRunning = false
@ -134,7 +138,10 @@ func (p *Player) Run(currSong Audio) error {
Paused: false,
}
p.mu.Lock()
p.format = &format
p.ctrl = ctrl
p.mu.Unlock()
resampler := beep.ResampleRatio(4, 1, ctrl)
volume := &effects.Volume{
@ -170,7 +177,7 @@ func (p *Player) Play() {
speaker.Unlock()
}
// Volume up and volume down using -0.5 or +0.5.
// SetVolume set volume up and volume down using -0.5 or +0.5.
func (p *Player) SetVolume(v float64) float64 {
// check if no songs playing currently
@ -186,7 +193,7 @@ func (p *Player) SetVolume(v float64) float64 {
return p.volume
}
// Toggles the pause state.
// TogglePause toggles the pause state.
func (p *Player) TogglePause() {
if p.ctrl == nil {
@ -200,7 +207,7 @@ func (p *Player) TogglePause() {
}
}
// Skips current song.
// Skip current song.
func (p *Player) Skip() {
p.execSongSkip(p.currentSong)
@ -210,17 +217,23 @@ func (p *Player) Skip() {
}
// drain the stream
speaker.Lock()
p.ctrl.Streamer = nil
p.streamSeekCloser.Close()
p.isRunning = false
p.format = nil
speaker.Unlock()
p.execSongFinish(p.currentSong)
}
// GetPosition returns the current position of audio file.
func (p *Player) GetPosition() time.Duration {
p.mu.Lock()
speaker.Lock()
defer speaker.Unlock()
defer p.mu.Unlock()
if p.format == nil || p.streamSeekCloser == nil {
return 1
}
@ -228,16 +241,22 @@ func (p *Player) GetPosition() time.Duration {
return p.format.SampleRate.D(p.streamSeekCloser.Position())
}
// seek is the function to move forward and rewind
// Seek is the function to move forward and rewind
func (p *Player) Seek(pos int) error {
p.mu.Lock()
speaker.Lock()
defer speaker.Unlock()
defer p.mu.Unlock()
err := p.streamSeekCloser.Seek(pos * int(p.format.SampleRate))
return err
}
// IsPaused is used to distinguish the player between pause and stop
func (p *Player) IsPaused() bool {
p.mu.Lock()
speaker.Lock()
defer speaker.Unlock()
defer p.mu.Unlock()
if p.ctrl == nil {
return false
}
@ -266,7 +285,7 @@ func (p *Player) IsRunning() bool {
return p.isRunning
}
// Gets the length of the song in the queue
// GetLength return the length of the song in the queue
func GetLength(audioPath string) (time.Duration, error) {
f, err := os.Open(audioPath)

View File

@ -7,6 +7,8 @@ import (
"fmt"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/rivo/tview"
@ -19,15 +21,16 @@ import (
// PlayingBar shows song name, progress and lyric
type PlayingBar struct {
*tview.Frame
full int
full int32
update chan struct{}
progress int
progress int32
skip bool
text *tview.TextView
hasTag bool
tag *id3v2.Tag
subtitle *lyric.Lyric
subtitles []*lyric.Lyric
mu sync.Mutex
}
func (p *PlayingBar) help() []string {
@ -60,9 +63,12 @@ func (p *PlayingBar) run() error {
for {
// stop progressing if song ends or skipped
if p.progress > p.full || p.skip {
progress := p.getProgress()
full := p.getFull()
if progress > full || p.skip {
p.skip = false
p.progress = 0
p.setProgress(0)
break
}
@ -71,21 +77,25 @@ func (p *PlayingBar) run() error {
continue
}
p.progress = int(gomu.player.GetPosition().Seconds())
// p.progress = int(gomu.player.GetPosition().Seconds())
p.setProgress(int(gomu.player.GetPosition().Seconds()))
start, err := time.ParseDuration(strconv.Itoa(p.progress) + "s")
start, err := time.ParseDuration(strconv.Itoa(progress) + "s")
if err != nil {
return tracerr.Wrap(err)
}
end, err := time.ParseDuration(strconv.Itoa(p.full) + "s")
end, err := time.ParseDuration(strconv.Itoa(full) + "s")
if err != nil {
return tracerr.Wrap(err)
}
var width int
gomu.app.QueueUpdate(func() {
_, _, width, _ = p.GetInnerRect()
})
_, _, width, _ := p.GetInnerRect()
progressBar := progresStr(p.progress, p.full, width/2, "█", "━")
progressBar := progresStr(progress, full, width/2, "█", "━")
// our progress bar
var lyricText string
if p.subtitle != nil {
@ -96,11 +106,11 @@ func (p *PlayingBar) run() error {
endTime = int32(p.subtitle.SyncedCaptions[i+1].Timestamp)
} else {
// Here we display the last lyric until the end of song
endTime = int32(p.full * 1000)
endTime = int32(full * 1000)
}
// here the currentTime is delayed 1 second because we want to show lyrics earlier
currentTime := int32(p.progress*1000) + 1000
currentTime := int32(progress*1000) + 1000
if currentTime >= startTime && currentTime <= endTime {
lyricText = p.subtitle.SyncedCaptions[i].Text
break
@ -135,8 +145,8 @@ func (p *PlayingBar) setSongTitle(title string) {
// Resets progress bar, ready for execution
func (p *PlayingBar) newProgress(currentSong *AudioFile, full int) {
p.full = full
p.progress = 0
p.full = int32(full)
p.setProgress(0)
p.setSongTitle(currentSong.name)
p.hasTag = false
p.tag = nil
@ -300,3 +310,14 @@ func (p *PlayingBar) loadLyrics(currentSongPath string) error {
return nil
}
func (p *PlayingBar) getProgress() int {
return int(atomic.LoadInt32(&p.progress))
}
func (p *PlayingBar) setProgress(progress int) {
atomic.StoreInt32(&p.progress, int32(progress))
}
func (p *PlayingBar) getFull() int {
return int(atomic.LoadInt32(&p.full))
}

View File

@ -737,6 +737,13 @@ func populate(root *tview.TreeNode, rootPath string, sortMtime bool) error {
parent: root,
}
audioLength, err := getTagLength(audioFile.path)
if err != nil {
logError(err)
}
audioFile.length = audioLength
displayText := setDisplayText(audioFile)
child.SetReference(audioFile)
@ -842,23 +849,3 @@ func setDisplayText(audioFile *AudioFile) string {
emojiDir := gomu.anko.GetString("Emoji.playlist")
return fmt.Sprintf(" %s %s", emojiDir, audioFile.name)
}
// populateAudioLength is the most time consuming part of startup,
// so here we initialize it separately
func populateAudioLength(root *tview.TreeNode) error {
root.Walk(func(node *tview.TreeNode, _ *tview.TreeNode) bool {
audioFile := node.GetReference().(*AudioFile)
if audioFile.isAudioFile {
audioLength, err := getTagLength(audioFile.path)
if err != nil {
logError(err)
return false
}
audioFile.length = audioLength
}
return true
})
gomu.queue.updateTitle()
return nil
}

View File

@ -155,18 +155,6 @@ func (q *Queue) dequeue() (*AudioFile, error) {
// Add item to the list and returns the length of the queue
func (q *Queue) enqueue(audioFile *AudioFile) (int, error) {
isTestEnv := os.Getenv("TEST") == "false"
if !gomu.player.IsRunning() && !gomu.player.IsPaused() && isTestEnv {
err := gomu.player.Run(audioFile)
if err != nil {
die(err)
}
return q.GetItemCount(), nil
}
if !audioFile.isAudioFile {
return q.GetItemCount(), nil
}
@ -430,3 +418,25 @@ func (q *Queue) updateQueueNames() error {
q.loadQueue()
return nil
}
// playQueue play the first item in the queue
func (q *Queue) playQueue() error {
isTestEnv := os.Getenv("TEST") == "false"
if !gomu.player.IsRunning() && !gomu.player.IsPaused() && isTestEnv {
audioFile, err := q.dequeue()
if err != nil {
return tracerr.Wrap(err)
}
err = gomu.player.Run(audioFile)
if err != nil {
return tracerr.Wrap(err)
}
return nil
}
return nil
}

View File

@ -71,7 +71,7 @@ func defineInternals() {
playlist, _ := gomu.anko.NewModule("Playlist")
playlist.Define("get_focused", gomu.playlist.getCurrentFile)
playlist.Define("focus", func(filepath string) {
root := gomu.playlist.GetRoot()
root.Walk(func(node, _ *tview.TreeNode) bool {
@ -372,7 +372,9 @@ func start(application *tview.Application, args Args) {
}
audioFile := audio.(*AudioFile)
gomu.playingBar.newProgress(audioFile, int(duration.Seconds()))
name := audio.Name()
var description string
@ -392,6 +394,7 @@ func start(application *tview.Application, args Args) {
logError(err)
}
}()
})
gomu.player.SetSongFinish(func(currAudio player.Audio) {
@ -436,6 +439,12 @@ func start(application *tview.Application, args Args) {
}
}
if len(gomu.queue.items) > 0 {
if err := gomu.queue.playQueue(); err != nil {
logError(err)
}
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
@ -526,7 +535,6 @@ func start(application *tview.Application, args Args) {
}
})
go populateAudioLength(gomu.playlist.GetRoot())
gomu.app.SetRoot(gomu.pages, true).SetFocus(gomu.playlist)
// main loop

View File

@ -259,22 +259,24 @@ func tagPopup(node *AudioFile) (err error) {
options = newOptions
// Update dropdown options
lyricDropDown.SetOptions(newOptions, nil).
SetCurrentOption(0).
SetSelectedFunc(func(text string, _ int) {
lyricTextView.SetText(popupLyricMap[text]).
SetTitle(" " + text + " lyric preview ")
})
gomu.app.QueueUpdateDraw(func() {
lyricDropDown.SetOptions(newOptions, nil).
SetCurrentOption(0).
SetSelectedFunc(func(text string, _ int) {
lyricTextView.SetText(popupLyricMap[text]).
SetTitle(" " + text + " lyric preview ")
})
// Update lyric preview
if len(newOptions) > 0 {
_, langExt := lyricDropDown.GetCurrentOption()
lyricTextView.SetText(popupLyricMap[langExt]).
SetTitle(" " + langExt + " lyric preview ")
} else {
lyricTextView.SetText("No lyric embeded.").
SetTitle(" lyric preview ")
}
// Update lyric preview
if len(newOptions) > 0 {
_, langExt := lyricDropDown.GetCurrentOption()
lyricTextView.SetText(popupLyricMap[langExt]).
SetTitle(" " + langExt + " lyric preview ")
} else {
lyricTextView.SetText("No lyric embeded.").
SetTitle(" lyric preview ")
}
})
}()
}).
SetBackgroundColorActivated(gomu.colors.popup).
@ -304,7 +306,9 @@ func tagPopup(node *AudioFile) (err error) {
SetWrap(true).
SetBorder(true)
lyricTextView.SetChangedFunc(func() {
lyricTextView.ScrollToBeginning()
gomu.app.QueueUpdate(func() {
lyricTextView.ScrollToBeginning()
})
})
leftGrid.SetRows(3, 1, 3, 3, 3, 3, 0, 3, 3, 1, 3, 3).