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) }