Commit 80305331 authored by Trevor Slocum's avatar Trevor Slocum

Add queue

Resolves #7.
parent c50589ef
Pipeline #113022458 passed with stages
in 2 minutes and 26 seconds
......@@ -10,12 +10,6 @@ fmt:
- gofmt -l -s -e .
- exit $(gofmt -l -s -e . | wc -l)
lint:
stage: validate
script:
- go get -u golang.org/x/lint/golint
- golint -set_exit_status
vet:
stage: validate
script:
......
0.1.5:
- Add initial volume configuration option and flag
- Add queue feature
- Use default terminal colors
0.1.4:
......
......@@ -2,8 +2,9 @@ This document covers the [ditty](https://gitlab.com/tslocum/ditty)
configuration options and their defaults.
# Options
* **--buffer-size** Audio buffer size, defaults to 500ms.
* **--autoplay-queue** Start playing when the queue is added to.
* **--restrict-library** Restrict access to a folder and its subfolders.
* **--fd** Write audio in WAV format to the specified file descriptor instead. This allows ditty to be used over ssh:
* `ssh ditty.rocketnine.space -t 'ditty --fd=2' 2> >(aplay --quiet)`
......@@ -13,13 +14,16 @@ configuration options and their defaults.
* **Select** Enter
* **Pause** Space
* **Refresh** R
* **Toggle hidden folder visibility** .
* **Browse parent folder and focus last** Backspace
* **Queue** Q
* **Delete from queue** D
* **Toggle focused list** Tab
* **Browse items** J/K, Down/Up and PageDown/PageUp
* **Previous track** P
* **Next track** N
* **Volume** -/+/M
* **Refresh** R
* **Toggle hidden folder visibility** .
* **Browse parent folder and focus last** Backspace
* **Exit** Escape
# Default ~/.config/ditty/config.yaml
......@@ -34,6 +38,12 @@ input:
- 'Space'
refresh:
- 'r'
queue:
- 'q'
delete:
- 'd'
focus-next:
- 'Tab'
hidden-folders:
- '.'
browse-parent:
......
......@@ -30,9 +30,7 @@ Choose one of the following methods:
### Compile
```
go get gitlab.com/tslocum/ditty
```
```go get gitlab.com/tslocum/ditty```
## Dependencies
......@@ -49,4 +47,4 @@ See [CONFIGURATION.md](https://gitlab.com/tslocum/ditty/blob/master/CONFIGURATIO
## Support
Please share issues/suggestions [here](https://gitlab.com/tslocum/ditty/issues).
Please share issues and suggestions [here](https://gitlab.com/tslocum/ditty/issues).
......@@ -165,8 +165,7 @@ func play(audioFile *audioFile) {
}
go app.QueueUpdateDraw(func() {
updateMain()
updateQueue()
updateLists()
updateStatus()
})
}
......@@ -187,10 +186,10 @@ func pause() {
}
func nextTrack() {
if mainBufferCursor-1 < len(mainBufferFiles)-1 {
mainBufferCursor++
if queueCursor < len(queueFiles)-1 {
queueCursor++
entry := selectedEntry()
entry := selectedQueueEntry()
audioFile, err := openFile(path.Join(mainBufferDirectory, entry.File.Name()), entry.Metadata)
if err != nil {
return
......@@ -202,25 +201,21 @@ func nextTrack() {
}
func skipPrevious() {
if mainBufferCursor > 1 {
if offsetEntry(-1).File.IsDir() {
return
}
listPrevious()
go listSelect()
if offsetQueueEntry(-1) == nil {
return
}
queuePrevious()
go queueSelect()
}
func skipNext() {
if mainBufferCursor < len(mainBufferFiles) {
if offsetEntry(1).File.IsDir() {
return
}
listNext()
go listSelect()
if offsetQueueEntry(1) == nil {
return
}
queueNext()
go queueSelect()
}
func roundUnit(x, unit float64) float64 {
......
......@@ -26,19 +26,30 @@ var (
bottomstatusbuf *cview.TextView
mainBufferFiles []*libraryEntry
mainBufferCursor int
mainBufferCursor = 1 // Position cursor on first entry
mainBufferDirectory string
mainBufferOrigin int
mainBufHeight int
mainBufferAutoFocus string // Entry path to focus after loading display
mainBuffer bytes.Buffer
mainLock = new(sync.Mutex)
queueFiles []*libraryEntry
queueCursor int
queueDirectory string
queueOrigin int
queueHeight int
queueBuffer bytes.Buffer
queueLock = new(sync.Mutex)
queueFocused bool
seekStart, seekEnd int
volumeStart, volumeEnd int
screenWidth, screenHeight int
mainBufHeight int
mainBuffer bytes.Buffer
mainLock = new(sync.Mutex)
statusText string
statusBuffer bytes.Buffer
......@@ -82,6 +93,11 @@ func initTUI() error {
return mainbuf.GetInnerRect()
})
queuebuf.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (i int, i2 int, i3 int, i4 int) {
queueHeight = height
return queuebuf.GetInnerRect()
})
app.SetRoot(grid, true)
return nil
......@@ -204,21 +220,25 @@ func updateMain() {
var printed int
var line string
if mainBufferOrigin == 0 {
if mainBufferCursor == 0 {
mainBuffer.WriteString("[::r]")
}
writeListItemPrefix(&mainBuffer, !queueFocused, mainBufferCursor, 0)
if mainBufferDirectory == "/" {
line = "./"
} else {
line = "../"
}
mainBuffer.WriteString(line)
if queueFocused {
writeListItemSuffix(&mainBuffer, !queueFocused, mainBufferCursor, 0)
}
for i := len(line); i < screenWidth-2; i++ {
mainBuffer.WriteRune(' ')
}
if mainBufferCursor == 0 {
mainBuffer.WriteString("[-:-:-]")
if !queueFocused {
writeListItemSuffix(&mainBuffer, !queueFocused, mainBufferCursor, 0)
}
printed++
}
for i, entry := range mainBufferFiles {
......@@ -230,20 +250,25 @@ func updateMain() {
mainBuffer.WriteRune('\n')
}
if i == mainBufferCursor-1 {
mainBuffer.WriteString("[::r]")
}
writeListItemPrefix(&mainBuffer, !queueFocused, mainBufferCursor-1, i)
if entry.File.IsDir() {
line = entry.File.Name() + "/"
} else {
line = entry.String()
}
mainBuffer.WriteString(line)
if queueFocused {
writeListItemSuffix(&mainBuffer, !queueFocused, mainBufferCursor-1, i)
}
for i := runewidth.StringWidth(line); i < screenWidth-2; i++ {
mainBuffer.WriteRune(' ')
}
if i == mainBufferCursor-1 {
mainBuffer.WriteString("[-:-:-]")
if !queueFocused {
writeListItemSuffix(&mainBuffer, !queueFocused, mainBufferCursor-1, i)
}
printed++
......@@ -256,7 +281,77 @@ func updateMain() {
}
func updateQueue() {
// TODO
queueLock.Lock()
defer queueLock.Unlock()
queueBuffer.Reset()
var printed int
var line string
for i, entry := range queueFiles {
if i < queueOrigin || i-queueOrigin > queueHeight-1 {
continue
}
if printed > 0 {
queueBuffer.WriteRune('\n')
}
writeListItemPrefix(&queueBuffer, queueFocused, queueCursor, i)
if entry.File.IsDir() {
line = entry.File.Name() + "/"
} else {
line = entry.String()
}
queueBuffer.WriteString(line)
if !queueFocused {
writeListItemSuffix(&queueBuffer, queueFocused, queueCursor, i)
}
for i := runewidth.StringWidth(line); i < screenWidth-2; i++ {
queueBuffer.WriteRune(' ')
}
if queueFocused {
writeListItemSuffix(&queueBuffer, queueFocused, queueCursor, i)
}
printed++
if printed == queueHeight-2 {
break
}
}
queuebuf.SetText(queueBuffer.String())
}
func writeListItemPrefix(buffer *bytes.Buffer, focused bool, cursor int, i int) {
if focused {
if i == cursor {
buffer.WriteString("[::r]")
}
} else {
if i == cursor {
buffer.WriteString("[::bu]")
}
}
}
func writeListItemSuffix(buffer *bytes.Buffer, focused bool, cursor int, i int) {
if focused {
if i == cursor {
buffer.WriteString("[-:-:-]")
}
} else {
if i == cursor {
buffer.WriteString("[-:-:-]")
}
}
}
func updateLists() {
updateMain()
updateQueue()
}
func updateStatus() {
......
......@@ -12,6 +12,9 @@ const (
actionSelect = "select"
actionPause = "pause"
actionRefresh = "refresh"
actionQueue = "queue"
actionDelete = "delete"
actionFocusNext = "focus-next"
actionToggleHidden = "hidden-folders"
actionBrowseParent = "browse-parent"
actionVolumeMute = "volume-mute"
......@@ -30,6 +33,9 @@ var actionHandlers = map[string]func(){
actionSelect: listSelect,
actionPause: pause,
actionRefresh: listRefresh,
actionQueue: listQueue,
actionDelete: listDelete,
actionFocusNext: toggleFocusedList,
actionToggleHidden: listToggleHidden,
actionBrowseParent: browseParent,
actionVolumeMute: toggleMute,
......@@ -87,6 +93,9 @@ func setDefaultKeyBinds() {
actionSelect: {"Enter"},
actionPause: {"Space"},
actionRefresh: {"r"},
actionQueue: {"q"},
actionDelete: {"d"},
actionFocusNext: {"Tab"},
actionToggleHidden: {"."},
actionBrowseParent: {"Backspace"},
actionVolumeMute: {"m"},
......
......@@ -6,34 +6,83 @@ import (
)
func listPrevious() {
if mainBufferOrigin > 0 && mainBufferCursor == mainBufferOrigin {
mainBufferOrigin--
}
if mainBufferCursor > 0 {
mainBufferCursor--
if !queueFocused {
if mainBufferOrigin > 0 && mainBufferCursor == mainBufferOrigin {
mainBufferOrigin--
}
if mainBufferCursor > 0 {
mainBufferCursor--
}
} else {
if queueOrigin > 0 && queueCursor == queueOrigin {
queueOrigin--
}
if queueCursor > 0 {
queueCursor--
}
}
go app.QueueUpdateDraw(updateMain)
go app.QueueUpdateDraw(updateLists)
}
func listNext() {
if mainBufferCursor < len(mainBufferFiles) {
mainBufferCursor++
if mainBufferCursor-mainBufferOrigin > mainBufHeight-3 {
mainBufferOrigin++
if !queueFocused {
if mainBufferCursor < len(mainBufferFiles) {
mainBufferCursor++
if mainBufferCursor-mainBufferOrigin > mainBufHeight-3 {
mainBufferOrigin++
}
}
} else {
if queueCursor < len(queueFiles)-1 {
queueCursor++
if queueCursor-queueOrigin > queueHeight-3 {
queueOrigin++
}
}
}
go app.QueueUpdateDraw(updateMain)
go app.QueueUpdateDraw(updateLists)
}
func queuePrevious() {
if queueOrigin > 0 && queueCursor == queueOrigin {
queueOrigin--
}
if queueCursor > 0 {
queueCursor--
}
go app.QueueUpdateDraw(updateQueue)
}
func queueNext() {
if queueCursor < len(queueFiles)-1 {
queueCursor++
queueOrigin = queueCursor - ((queueHeight - 3) / 2)
if queueOrigin < 0 {
queueOrigin = 0
}
if queueOrigin > (len(queueFiles)-1)-(queueHeight-3) {
queueOrigin = (len(queueFiles) - 1) - (queueHeight - 3)
}
}
go app.QueueUpdateDraw(updateQueue)
}
func listSelect() {
if queueFocused {
queueSelect()
return
}
if mainBufferCursor == 0 {
browseFolder(path.Join(mainBufferDirectory, ".."))
return
}
entry := selectedEntry()
entry := selectedMainEntry()
if entry.File.IsDir() {
browseFolder(path.Join(mainBufferDirectory, path.Base(entry.File.Name())))
return
......@@ -55,48 +104,179 @@ func listSelect() {
go app.QueueUpdateDraw(updateStatus)
}
func selectedEntry() *libraryEntry {
func queueSelect() {
entry := selectedQueueEntry()
if entry.File.IsDir() {
// TODO error
return
}
audioFile, err := openFile(entry.Path, entry.Metadata)
if err != nil {
statusText = err.Error()
go func() {
time.Sleep(5 * time.Second)
statusText = ""
go app.QueueUpdateDraw(updateMain)
}()
go app.QueueUpdateDraw(updateMain)
return
}
go play(audioFile)
go app.QueueUpdateDraw(updateStatus)
}
func listQueue() {
if mainBufferCursor == 0 {
// TODO Show error
return
}
entry := selectedMainEntry()
if entry == nil {
return
} else if entry.File.IsDir() {
scanFiles := scanFolderRecursively(entry.Path)
queueLock.Lock()
queueFiles = append(queueFiles, scanFiles...)
queueLock.Unlock()
if playOnQueue && playingFileID == 0 && len(queueFiles) > 0 {
queueSelect()
}
go app.QueueUpdateDraw(updateQueue)
return
}
queueLock.Lock()
defer queueLock.Unlock()
queueFiles = append(queueFiles, entry)
if playOnQueue && playingFileID == 0 {
queueSelect()
}
go app.QueueUpdateDraw(updateLists)
}
func listDelete() {
if !queueFocused {
return
}
queueLock.Lock()
defer queueLock.Unlock()
if len(queueFiles) <= queueCursor {
return
}
queueFiles = append(queueFiles[:queueCursor], queueFiles[queueCursor+1:]...)
if queueCursor > len(queueFiles)-1 {
queueCursor = len(queueFiles) - 1
if queueCursor < 0 {
queueCursor = 0
}
}
go app.QueueUpdateDraw(updateQueue)
}
func selectedMainEntry() *libraryEntry {
return mainBufferFiles[mainBufferCursor-1]
}
func offsetEntry(offset int) *libraryEntry {
func offsetMainEntry(offset int) *libraryEntry {
if (mainBufferCursor-1)+offset < 0 || (mainBufferCursor-1)+offset >= len(mainBufferFiles) {
return nil
}
return mainBufferFiles[(mainBufferCursor-1)+offset]
}
func selectedQueueEntry() *libraryEntry {
return queueFiles[queueCursor]
}
func offsetQueueEntry(offset int) *libraryEntry {
if queueCursor+offset < 0 || queueCursor+offset >= len(queueFiles) {
return nil
}
return queueFiles[queueCursor+offset]
}
func listPreviousPage() {
if mainBufferOrigin == 0 {
mainBufferCursor = 0
if !queueFocused {
if mainBufferOrigin == 0 {
mainBufferCursor = 0
go app.QueueUpdateDraw(updateMain)
return
}
mainBufferOrigin -= (mainBufHeight - 1) - 2
if mainBufferOrigin < 0 {
mainBufferOrigin = 0
}
mainBufferCursor = mainBufferOrigin
go app.QueueUpdateDraw(updateMain)
return
}
} else {
if queueOrigin == 0 {
queueCursor = 0
mainBufferOrigin -= (mainBufHeight - 1) - 2
if mainBufferOrigin < 0 {
mainBufferOrigin = 0
}
mainBufferCursor = mainBufferOrigin
go app.QueueUpdateDraw(updateQueue)
return
}
go app.QueueUpdateDraw(updateMain)
queueOrigin -= (queueHeight - 1) - 2
if queueOrigin < 0 {
queueOrigin = 0
}
queueCursor = queueOrigin
go app.QueueUpdateDraw(updateQueue)
}
}
func listNextPage() {
numEntries := len(mainBufferFiles)
if !queueFocused {
numEntries := len(mainBufferFiles)
if mainBufferOrigin >= numEntries-(mainBufHeight-1) {
mainBufferCursor = numEntries
go app.QueueUpdateDraw(updateMain)
return
}
if mainBufferOrigin >= numEntries-(mainBufHeight-1) {
mainBufferCursor = numEntries
mainBufferOrigin += (mainBufHeight - 1) - 2
if mainBufferOrigin > (numEntries-(mainBufHeight-1))+2 {
mainBufferOrigin = (numEntries - (mainBufHeight - 1)) + 2
}
mainBufferCursor = mainBufferOrigin
go app.QueueUpdateDraw(updateMain)
return
}
} else {
numEntries := len(queueFiles)
mainBufferOrigin += (mainBufHeight - 1) - 2