Refactor Queue to use cancellable entries
This commit is contained in:
parent
24493a659b
commit
efa01d5c31
45
audio/queue/entry.go
Normal file
45
audio/queue/entry.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntryCallback func(q *Queue, entry *Entry)
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
Identifier Identifier
|
||||||
|
Source audio.Source
|
||||||
|
ReadSamples atomic.Uint64
|
||||||
|
cancel chan struct{}
|
||||||
|
cancelMutex sync.Mutex
|
||||||
|
StartCallback EntryCallback
|
||||||
|
EndCallback EntryCallback
|
||||||
|
RemoveCallback EntryCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(identifier Identifier, source audio.Source, cancel chan struct{}, start, end, remove EntryCallback) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Identifier: identifier,
|
||||||
|
Source: source,
|
||||||
|
cancel: cancel,
|
||||||
|
StartCallback: start,
|
||||||
|
EndCallback: end,
|
||||||
|
RemoveCallback: remove,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) Done() <-chan struct{} {
|
||||||
|
return e.cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) Cancel() {
|
||||||
|
e.cancelMutex.Lock()
|
||||||
|
defer e.cancelMutex.Unlock()
|
||||||
|
select {
|
||||||
|
case <-e.Done():
|
||||||
|
default:
|
||||||
|
close(e.cancel)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,31 +4,20 @@ import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
|
||||||
"log"
|
"log"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QueueIdentifier int
|
type Identifier int
|
||||||
|
|
||||||
type QueueEntry struct {
|
|
||||||
Identifier QueueIdentifier
|
|
||||||
Source audio.Source
|
|
||||||
ReadSamples atomic.Uint64
|
|
||||||
cancel chan bool
|
|
||||||
StartCallback func(q *Queue, entry *QueueEntry)
|
|
||||||
EndCallback func(q *Queue, entry *QueueEntry)
|
|
||||||
RemoveCallback func(q *Queue, entry *QueueEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Queue struct {
|
type Queue struct {
|
||||||
queue []*QueueEntry
|
queue []*Entry
|
||||||
output audio.Source
|
output audio.Source
|
||||||
interrupt chan bool
|
closed atomic.Bool
|
||||||
interruptDepth atomic.Int64
|
|
||||||
closed bool
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
identifierCounter QueueIdentifier
|
identifierCounter Identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Queue {
|
func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Queue {
|
||||||
|
@ -36,9 +25,7 @@ func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Qu
|
||||||
log.Panicf("not allowed channel number %d", channels)
|
log.Panicf("not allowed channel number %d", channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
q := &Queue{
|
q := &Queue{}
|
||||||
interrupt: make(chan bool, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case audio.SourceFloat32:
|
case audio.SourceFloat32:
|
||||||
|
@ -56,8 +43,8 @@ func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Qu
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
func spliceHelper[T audio.AllowedSourceTypes](input audio.TypedSource[T]) (output audio.TypedSource[T], cancel chan bool) {
|
func spliceHelper[T audio.AllowedSourceTypes](input audio.TypedSource[T]) (output audio.TypedSource[T], cancel chan struct{}) {
|
||||||
cancel = make(chan bool, 1)
|
cancel = make(chan struct{}, 1)
|
||||||
output = audio.NewSource[T](input.GetBitDepth(), input.GetSampleRate(), input.GetChannels())
|
output = audio.NewSource[T](input.GetBitDepth(), input.GetSampleRate(), input.GetChannels())
|
||||||
|
|
||||||
bitDepth := input.GetBitDepth()
|
bitDepth := input.GetBitDepth()
|
||||||
|
@ -89,7 +76,7 @@ func spliceHelper[T audio.AllowedSourceTypes](input audio.TypedSource[T]) (outpu
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) spliceSources(input audio.Source) (audio.Source, chan bool) {
|
func (q *Queue) spliceSources(input audio.Source) (audio.Source, chan struct{}) {
|
||||||
|
|
||||||
switch q.GetSource().GetFormat() {
|
switch q.GetSource().GetFormat() {
|
||||||
case audio.SourceFloat32:
|
case audio.SourceFloat32:
|
||||||
|
@ -105,56 +92,75 @@ func (q *Queue) spliceSources(input audio.Source) (audio.Source, chan bool) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queue) current() *Entry {
|
||||||
|
q.lock.RLock()
|
||||||
|
defer q.lock.RUnlock()
|
||||||
|
if len(q.queue) > 0 {
|
||||||
|
return q.queue[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func queueLoopStart[T audio.AllowedSourceTypes](q *Queue) {
|
func queueLoopStart[T audio.AllowedSourceTypes](q *Queue) {
|
||||||
q.wg.Add(1)
|
q.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer q.wg.Done()
|
defer q.wg.Done()
|
||||||
var current *QueueEntry
|
var current *Entry
|
||||||
var currentBlocks chan []T
|
|
||||||
L1:
|
L1:
|
||||||
for {
|
for {
|
||||||
q.lock.RLock()
|
if q.closed.Load() {
|
||||||
if q.closed {
|
|
||||||
q.output.Close()
|
q.output.Close()
|
||||||
break L1
|
break L1
|
||||||
}
|
}
|
||||||
if len(q.queue) == 0 { //no more entries, wait for interrupt
|
|
||||||
q.lock.RUnlock()
|
if func() bool {
|
||||||
<-q.interrupt
|
currentEntry := q.current()
|
||||||
q.interruptDepth.Add(-1)
|
if currentEntry == nil {
|
||||||
|
return current == nil
|
||||||
|
} else if current == nil || current.Identifier != currentEntry.Identifier {
|
||||||
|
current = currentEntry
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}() {
|
||||||
|
runtime.Gosched()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if current == nil || current.Identifier != q.queue[0].Identifier {
|
|
||||||
current = q.queue[0]
|
|
||||||
currentBlocks = current.Source.(audio.TypedSource[T]).GetBlocks()
|
|
||||||
}
|
|
||||||
q.lock.RUnlock()
|
|
||||||
|
|
||||||
F1:
|
func() {
|
||||||
for {
|
currentBlocks := current.Source.(audio.TypedSource[T]).GetBlocks()
|
||||||
select {
|
defer current.Source.Unlock()
|
||||||
case <-q.interrupt:
|
|
||||||
q.interruptDepth.Add(-1)
|
if current.StartCallback != nil && current.ReadSamples.Load() == 0 {
|
||||||
//force recheck
|
current.StartCallback(q, current)
|
||||||
break F1
|
}
|
||||||
case block, more := <-currentBlocks:
|
|
||||||
if !more {
|
output := q.output.(audio.TypedSource[T])
|
||||||
//no more blocks! skip
|
|
||||||
if current.EndCallback != nil {
|
for {
|
||||||
current.EndCallback(q, current)
|
select {
|
||||||
|
case <-current.Done():
|
||||||
|
//song has been cancelled elsewhere
|
||||||
|
return
|
||||||
|
case block, more := <-currentBlocks:
|
||||||
|
if !more {
|
||||||
|
//no more blocks to read
|
||||||
|
current.Cancel()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
current.ReadSamples.Add(uint64(len(block) / current.Source.GetChannels()))
|
||||||
|
output.IngestNative(block, current.Source.GetBitDepth())
|
||||||
}
|
}
|
||||||
q.Remove(current.Identifier)
|
|
||||||
break F1
|
|
||||||
} else {
|
|
||||||
if current.StartCallback != nil && current.ReadSamples.Load() == 0 && len(block) > 0 {
|
|
||||||
current.StartCallback(q, current)
|
|
||||||
}
|
|
||||||
current.ReadSamples.Add(uint64(len(block) / current.Source.GetChannels()))
|
|
||||||
q.output.(audio.TypedSource[T]).IngestNative(block, current.Source.GetBitDepth())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
|
||||||
|
{
|
||||||
|
//no more blocks! skip
|
||||||
|
if current.EndCallback != nil {
|
||||||
|
current.EndCallback(q, current)
|
||||||
|
}
|
||||||
|
q.Remove(current.Identifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
@ -172,90 +178,75 @@ func (q *Queue) getFilterChain(source audio.Source) audio.Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) AddHead(source audio.Source, startCallback func(q *Queue, entry *QueueEntry), endCallback func(q *Queue, entry *QueueEntry), removeCallback func(q *Queue, entry *QueueEntry)) (identifier QueueIdentifier) {
|
func (q *Queue) AddHead(source audio.Source, startCallback, endCallback, removeCallback EntryCallback) (identifier Identifier) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
splicedOutput, cancel := q.spliceSources(source)
|
splicedOutput, cancel := q.spliceSources(source)
|
||||||
identifier = q.identifierCounter
|
identifier = q.identifierCounter
|
||||||
|
|
||||||
|
entry := NewEntry(identifier, q.getFilterChain(splicedOutput), cancel, startCallback, endCallback, removeCallback)
|
||||||
if len(q.queue) > 0 {
|
if len(q.queue) > 0 {
|
||||||
q.queue = append(q.queue[:1], append([]*QueueEntry{{
|
q.queue = append(q.queue[:1], append([]*Entry{entry}, q.queue[1:]...)...)
|
||||||
Identifier: identifier,
|
|
||||||
Source: q.getFilterChain(splicedOutput),
|
|
||||||
cancel: cancel,
|
|
||||||
StartCallback: startCallback,
|
|
||||||
EndCallback: endCallback,
|
|
||||||
RemoveCallback: removeCallback,
|
|
||||||
}}, q.queue[1:]...)...)
|
|
||||||
} else {
|
} else {
|
||||||
q.queue = append(q.queue, &QueueEntry{
|
q.queue = append(q.queue, entry)
|
||||||
Identifier: identifier,
|
|
||||||
Source: q.getFilterChain(splicedOutput),
|
|
||||||
cancel: cancel,
|
|
||||||
StartCallback: startCallback,
|
|
||||||
EndCallback: endCallback,
|
|
||||||
RemoveCallback: removeCallback,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
q.identifierCounter++
|
q.identifierCounter++
|
||||||
q.lock.Unlock()
|
|
||||||
q.sendInterrupt()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) AddTail(source audio.Source, startCallback func(q *Queue, entry *QueueEntry), endCallback func(q *Queue, entry *QueueEntry), removeCallback func(q *Queue, entry *QueueEntry)) (identifier QueueIdentifier) {
|
func (q *Queue) AddTail(source audio.Source, startCallback, endCallback, removeCallback EntryCallback) (identifier Identifier) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
splicedOutput, cancel := q.spliceSources(source)
|
splicedOutput, cancel := q.spliceSources(source)
|
||||||
identifier = q.identifierCounter
|
identifier = q.identifierCounter
|
||||||
q.queue = append(q.queue, &QueueEntry{
|
|
||||||
Identifier: identifier,
|
entry := NewEntry(identifier, q.getFilterChain(splicedOutput), cancel, startCallback, endCallback, removeCallback)
|
||||||
Source: q.getFilterChain(splicedOutput),
|
|
||||||
cancel: cancel,
|
q.queue = append(q.queue, entry)
|
||||||
StartCallback: startCallback,
|
|
||||||
EndCallback: endCallback,
|
|
||||||
RemoveCallback: removeCallback,
|
|
||||||
})
|
|
||||||
q.identifierCounter++
|
q.identifierCounter++
|
||||||
q.lock.Unlock()
|
|
||||||
q.sendInterrupt()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) IsClosed() bool {
|
func (q *Queue) IsClosed() bool {
|
||||||
q.lock.RLock()
|
return q.closed.Load()
|
||||||
defer q.lock.RUnlock()
|
|
||||||
return q.closed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) Remove(identifier QueueIdentifier) bool {
|
func (q *Queue) Remove(identifier Identifier) bool {
|
||||||
q.lock.Lock()
|
var entry *Entry
|
||||||
|
|
||||||
for i, e := range q.queue {
|
func() {
|
||||||
if e.Identifier == identifier {
|
q.lock.Lock()
|
||||||
q.sendInterrupt()
|
defer q.lock.Unlock()
|
||||||
e.cancel <- true
|
|
||||||
|
|
||||||
e.Source.Unlock()
|
for i, e := range q.queue {
|
||||||
|
if e.Identifier == identifier {
|
||||||
|
e.Cancel()
|
||||||
|
|
||||||
go audio.NewNullSink().Process(e.Source)
|
e.Source.Unlock()
|
||||||
//delete entry
|
|
||||||
q.queue = append(q.queue[:i], q.queue[i+1:]...)
|
go audio.NewNullSink().Process(e.Source)
|
||||||
q.lock.Unlock()
|
//delete entry
|
||||||
if e.RemoveCallback != nil {
|
q.queue = append(q.queue[:i], q.queue[i+1:]...)
|
||||||
e.RemoveCallback(q, e)
|
entry = e
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if entry != nil {
|
||||||
|
if entry.RemoveCallback != nil {
|
||||||
|
entry.RemoveCallback(q, entry)
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
q.lock.Unlock()
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) GetQueueHead() *QueueEntry {
|
func (q *Queue) GetQueueHead() *Entry {
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
defer q.lock.RUnlock()
|
defer q.lock.RUnlock()
|
||||||
if len(q.queue) > 0 {
|
if len(q.queue) > 0 {
|
||||||
|
@ -264,7 +255,7 @@ func (q *Queue) GetQueueHead() *QueueEntry {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) GetQueueTail() (index int, entry *QueueEntry) {
|
func (q *Queue) GetQueueTail() (index int, entry *Entry) {
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
defer q.lock.RUnlock()
|
defer q.lock.RUnlock()
|
||||||
if len(q.queue) > 0 {
|
if len(q.queue) > 0 {
|
||||||
|
@ -273,7 +264,7 @@ func (q *Queue) GetQueueTail() (index int, entry *QueueEntry) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) GetQueueIndex(index int) *QueueEntry {
|
func (q *Queue) GetQueueIndex(index int) *Entry {
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
defer q.lock.RUnlock()
|
defer q.lock.RUnlock()
|
||||||
if len(q.queue) > index {
|
if len(q.queue) > index {
|
||||||
|
@ -282,7 +273,7 @@ func (q *Queue) GetQueueIndex(index int) *QueueEntry {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) GetQueueEntry(identifier QueueIdentifier) (index int, entry *QueueEntry) {
|
func (q *Queue) GetQueueEntry(identifier Identifier) (index int, entry *Entry) {
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
defer q.lock.RUnlock()
|
defer q.lock.RUnlock()
|
||||||
for i, e := range q.queue {
|
for i, e := range q.queue {
|
||||||
|
@ -300,11 +291,11 @@ func (q *Queue) GetQueueSize() int {
|
||||||
return len(q.queue)
|
return len(q.queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) GetQueue() (entries []*QueueEntry) {
|
func (q *Queue) GetQueue() (entries []*Entry) {
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
defer q.lock.RUnlock()
|
defer q.lock.RUnlock()
|
||||||
|
|
||||||
entries = make([]*QueueEntry, len(q.queue))
|
entries = make([]*Entry, len(q.queue))
|
||||||
copy(entries, q.queue)
|
copy(entries, q.queue)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -322,18 +313,11 @@ func (q *Queue) GetChannels() int {
|
||||||
return q.GetSource().GetChannels()
|
return q.GetSource().GetChannels()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) sendInterrupt() {
|
|
||||||
//TODO: maybe use len() on channel?
|
|
||||||
if q.interruptDepth.Load() == 0 { //not waiting on interrupt
|
|
||||||
q.interruptDepth.Add(1)
|
|
||||||
q.interrupt <- true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queue) Close() {
|
func (q *Queue) Close() {
|
||||||
if !q.closed {
|
if !q.closed.Swap(true) {
|
||||||
q.closed = true
|
if current := q.current(); current != nil {
|
||||||
q.sendInterrupt()
|
current.Cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,11 +39,11 @@ func TestQueue(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddTail(source, func(q *Queue, entry *QueueEntry) {
|
q.AddTail(source, func(q *Queue, entry *Entry) {
|
||||||
t.Logf("Started playback of %d %s\n", entry.Identifier, fullPath)
|
t.Logf("Started playback of %d %s\n", entry.Identifier, fullPath)
|
||||||
}, func(q *Queue, entry *QueueEntry) {
|
}, func(q *Queue, entry *Entry) {
|
||||||
t.Logf("Finished playback of %d %s: output %d samples\n", entry.Identifier, fullPath, entry.ReadSamples.Load())
|
t.Logf("Finished playback of %d %s: output %d samples\n", entry.Identifier, fullPath, entry.ReadSamples.Load())
|
||||||
}, func(q *Queue, entry *QueueEntry) {
|
}, func(q *Queue, entry *Entry) {
|
||||||
fp.Close()
|
fp.Close()
|
||||||
if q.GetQueueSize() == 0 {
|
if q.GetQueueSize() == 0 {
|
||||||
t.Log("Finished playback, closing\n")
|
t.Log("Finished playback, closing\n")
|
||||||
|
|
Loading…
Reference in a new issue