2022-09-03 14:20:40 +00:00
|
|
|
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
|
|
|
|
|
2022-10-02 13:19:51 +00:00
|
|
|
writer listener.WriterFunc
|
|
|
|
waiter chan struct{}
|
2022-09-03 14:20:40 +00:00
|
|
|
offsetStart bool
|
|
|
|
|
|
|
|
pendingMetadata map[string]string
|
|
|
|
}
|
|
|
|
|
2022-10-02 13:19:51 +00:00
|
|
|
func NewListener(info listener.Information, writer listener.WriterFunc, samplesToBuffer int64, offsetStart bool) (*Listener, map[string]string) {
|
2022-09-03 14:20:40 +00:00
|
|
|
headers := make(map[string]string)
|
|
|
|
headers["icy-metaint"] = strconv.Itoa(icyInterval)
|
|
|
|
|
|
|
|
return &Listener{
|
|
|
|
information: info,
|
2022-10-02 13:19:51 +00:00
|
|
|
writer: writer,
|
2022-09-03 14:20:40 +00:00
|
|
|
offsetStart: offsetStart,
|
|
|
|
streamSamplesBuffer: samplesToBuffer,
|
|
|
|
streamStartOffset: -1,
|
|
|
|
pendingMetadata: make(map[string]string),
|
2022-10-02 13:19:51 +00:00
|
|
|
waiter: make(chan struct{}),
|
2022-09-03 14:20:40 +00:00
|
|
|
}, headers
|
|
|
|
}
|
|
|
|
|
2022-10-02 13:19:51 +00:00
|
|
|
func (l *Listener) Wait() {
|
|
|
|
<-l.waiter
|
|
|
|
}
|
|
|
|
|
2022-09-03 14:20:40 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-02 13:19:51 +00:00
|
|
|
return l.writer(data)
|
2022-09-03 14:20:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Listener) Close() {
|
2022-10-02 13:19:51 +00:00
|
|
|
l.writer(nil)
|
|
|
|
close(l.waiter)
|
2022-09-03 14:20:40 +00:00
|
|
|
}
|