mirror of
https://github.com/issadarkthing/gomu.git
synced 2025-04-25 13:48:49 +08:00
Fix data race in playingbar.go and player.go
This commit is contained in:
parent
5dacd77cd1
commit
5d46449ddd
48
command.go
48
command.go
@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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())}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
27
playlist.go
27
playlist.go
@ -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
|
||||
}
|
||||
|
34
queue.go
34
queue.go
@ -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
|
||||
}
|
||||
|
12
start.go
12
start.go
@ -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
|
||||
|
36
tageditor.go
36
tageditor.go
@ -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).
|
||||
|
Loading…
x
Reference in New Issue
Block a user