MeteorLight/listener/icy/icy.go

157 lines
3.9 KiB
Go

package icy
import (
"fmt"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer"
"git.gammaspectra.live/S.O.N.G/MeteorLight/listener"
"git.gammaspectra.live/S.O.N.G/MeteorLight/queue/metadata"
"strconv"
"sync/atomic"
)
// icyInterval weird clients might not support other numbers than this
const icyInterval = 8192
type Listener struct {
information listener.Information
started atomic.Bool
streamStartOffset int64
streamSamplesBuffer int64
counter int
writer listener.WriterFunc
waiter chan struct{}
offsetStart bool
pendingMetadata map[string]string
}
func NewListener(info listener.Information, writer listener.WriterFunc, samplesToBuffer int64, offsetStart bool) (*Listener, map[string]string) {
headers := make(map[string]string)
headers["icy-metaint"] = strconv.Itoa(icyInterval)
return &Listener{
information: info,
writer: writer,
offsetStart: offsetStart,
streamSamplesBuffer: samplesToBuffer,
streamStartOffset: -1,
pendingMetadata: make(map[string]string),
waiter: make(chan struct{}),
}, headers
}
func (l *Listener) Wait() {
<-l.waiter
}
func (l *Listener) Identifier() string {
return l.information.Identifier
}
func (l *Listener) Information() *listener.Information {
return &l.information
}
func (l *Listener) HasStarted() bool {
return l.started.Load()
}
func (l *Listener) Start(packets []packetizer.Packet) error {
if l.started.Swap(true) {
return nil
}
if len(packets) > 0 {
sampleBufferMin := packets[len(packets)-1].GetStartSampleNumber() - l.streamSamplesBuffer
for _, p := range packets {
if p.KeepMode() != packetizer.Discard || p.GetEndSampleNumber() >= sampleBufferMin {
if err := l.Write(p); err != nil {
return err
}
}
}
}
return nil
}
func (l *Listener) writeIcy() []byte {
packetContent := make([]byte, 1, 16*255+1)
for k, v := range l.pendingMetadata {
packetContent = append(packetContent, []byte(fmt.Sprintf("%s='%s';", k, v))...)
delete(l.pendingMetadata, k)
//shouldn't send multiple properties in same packet if we want working single quotes, wait until next ICY frame
break
}
contentLength := len(packetContent) - 1
if contentLength > 16*255 {
//cannot send long titles
return make([]byte, 1)
}
if (contentLength % 16) == 0 { //already padded
packetContent[0] = byte(contentLength / 16)
} else {
packetContent[0] = byte(contentLength/16) + 1
packetContent = append(packetContent, make([]byte, 16-(contentLength%16))...)
}
return packetContent
}
func (l *Listener) Write(packet packetizer.Packet) error {
if metadataPacket, ok := packet.(*metadata.Packet); ok {
if len(metadataPacket.TrackEntry.Artist()) > 0 {
l.pendingMetadata["StreamTitle"] = fmt.Sprintf("%s - %s", metadataPacket.TrackEntry.Artist(), metadataPacket.TrackEntry.Title())
} else {
l.pendingMetadata["StreamTitle"] = metadataPacket.TrackEntry.Title()
}
if len(metadataPacket.TrackEntry.Metadata.Art) > 0 {
l.pendingMetadata["StreamURL"] = metadataPacket.TrackEntry.Metadata.Art
}
return nil
}
var p []byte
if offsetable, ok := packet.(packetizer.OffsetablePacket); l.offsetStart && ok {
if l.streamStartOffset <= -1 {
if offsetable.KeepMode() != packetizer.Keep {
l.streamStartOffset = offsetable.GetStartSampleNumber()
p = offsetable.GetDataOffset(l.streamStartOffset)
} else {
p = packet.GetData()
}
} else {
p = offsetable.GetDataOffset(l.streamStartOffset)
}
} else {
p = packet.GetData()
}
var data []byte
for len(p) > 0 {
length := icyInterval - l.counter
if length <= len(p) {
data = append(data, p[:length]...)
data = append(data, l.writeIcy()...)
l.counter = 0
p = p[length:]
} else {
data = append(data, p...)
l.counter += len(p)
p = p[:0]
}
}
return l.writer(data)
}
func (l *Listener) Close() {
_ = l.writer(nil)
close(l.waiter)
}