|
- package icecast
-
- import (
- "strings"
- "sync"
- )
-
- type RadioTextOptions struct {
- Enabled bool
- Prefix string
- MaxLen int
- OnlyOnChange bool
- }
-
- func mapStreamTitleToRadioText(streamTitle string, opts RadioTextOptions) string {
- if !opts.Enabled {
- return ""
- }
- maxLen := opts.MaxLen
- if maxLen <= 0 || maxLen > 64 {
- maxLen = 64
- }
- title := sanitizeASCII(streamTitle)
- if title == "" {
- return ""
- }
- prefixRaw := opts.Prefix
- prefixHadTrailingSpace := strings.TrimRight(prefixRaw, " \t\r\n") != prefixRaw
- prefix := sanitizeASCII(opts.Prefix)
- if prefix != "" && prefixHadTrailingSpace {
- prefix += " "
- }
- rt := title
- if prefix != "" {
- rt = prefix + title
- }
- if len(rt) > maxLen {
- rt = strings.TrimSpace(rt[:maxLen])
- }
- return rt
- }
-
- func sanitizeASCII(raw string) string {
- raw = strings.TrimSpace(raw)
- if raw == "" {
- return ""
- }
- var b strings.Builder
- b.Grow(len(raw))
- prevSpace := true
- for _, r := range raw {
- switch r {
- case '\n', '\r', '\t':
- r = ' '
- }
- if r < 0x20 || r == 0x7f || r > 0x7e {
- continue
- }
- if r == ' ' {
- if prevSpace {
- continue
- }
- prevSpace = true
- b.WriteByte(' ')
- continue
- }
- prevSpace = false
- b.WriteByte(byte(r))
- }
- return strings.TrimSpace(b.String())
- }
-
- type RadioTextRelay struct {
- opts RadioTextOptions
- apply func(string) error
- mu sync.Mutex
- lastRT string
- }
-
- func NewRadioTextRelay(opts RadioTextOptions, initialRT string, apply func(string) error) *RadioTextRelay {
- return &RadioTextRelay{
- opts: opts,
- apply: apply,
- lastRT: sanitizeASCII(initialRT),
- }
- }
-
- func (r *RadioTextRelay) HandleStreamTitle(streamTitle string) error {
- if r == nil || r.apply == nil {
- return nil
- }
- next := mapStreamTitleToRadioText(streamTitle, r.opts)
- if next == "" {
- return nil
- }
- r.mu.Lock()
- skip := r.opts.OnlyOnChange && next == r.lastRT
- if !skip {
- r.lastRT = next
- }
- r.mu.Unlock()
- if skip {
- return nil
- }
- return r.apply(next)
- }
|