Liquidsoap embedded metadata support for icecast radio stream

Issue description:

Metadata updates performed on an icecast2 ogg/flac stream created in liquidsoap are ignored in Symfonium and metadata updates seem to crash playback.

Logs:

655321 liquidsoap icecast.log

Upload description:
655321 - Liquidsoap embedded metadata support for icecast radio stream

Additional information:

I’ve recently revisited this topic where I tried to add metadata available via an API to an online radio stream that lacked this information.

I have achieved this outside of Symfonium by using the original stream as a source, parsing the API response and adding the metadata to a new version of the stream. I chose ogg/flac because the source stream is MP3 320KBit/s and I dislike transcoding lossy formats to other lossy formats. Flac bloats the size but leads to no further audio degradation and in my home network, ~1MBit/s of traffic is irrelevant.

On the off-chance that Tolriq is familiar with liquidsoup, here’s the code I use to achieve this. Don’t expect it to be good, I wanted to slam my head against a wall while writing it many, many times.

bbc6music.liq
# IceCast credentials
icecast_host = "192.168.1.53"
icecast_port = 8000
icecast_user = "source"
icecast_pass = "REDACTED"
mount_point = "/bbc6music.ogg"

# BBC Radio 6 Music stream URL
bbc_stream = "http://as-hls-ww-live.akamaized.net/pool_81827798/live/ww/bbc_6music/bbc_6music.isml/bbc_6music-audio%3D320000.norewind.m3u8"

# API url to fetch the current metadata
#api_url = "https://rms.api.bbc.co.uk/v2/services/bbc_6music/segments/latest"
api_url = "https://radio-metadata.fr/api?id=bbcradio6"

# Fetch audio stream
radio = mksafe(input.http(bbc_stream, clock_safe=true))

# Wrap the source with insert_metadata to enable metadata updates
radio = insert_metadata(radio)

def update_metadata()
  def get_api()
    api_response = process.read.lines("curl -s #{api_url}")
    json_string = list.hd(api_response) # Get the first line of JSON
    json_string
  end
  
  def parse_api_response(json_string)
    let json.parse ( x : {
      mode: string,
      data: {
        metadata: string,
        title: string?,
        artist: string?,
        cover: string
      },
    }) = json_string
    x
  end
  
  json_string = get_api()
  mdata = parse_api_response(json_string)

  # Set empty values to "unknown" as they are ignored by lastfm
  title = if null.defined(mdata.data.title) then mdata.data.title else "unknown" end
  artist = if null.defined(mdata.data.artist) then mdata.data.artist else "unknown" end
  cover = string.replace(pattern="800x800", (fun(_) -> "raw"), mdata.data.cover)

  if mdata.mode == "song" then
  # Log what's playing
    log("Playing #{mdata.data.title} by #{mdata.data.artist}")
  else
    log("Not playing music, currently listening to #{mdata.data.metadata}.")
  end

  # Insert metadata into the radio stream
  radio.insert_metadata([
    ("title", string_of(title)),
    ("artist", string_of(artist)),
    ("coverart", cover)
  ])
end
    
thread.run(update_metadata, every=30.)

# Stream to Icecast using FLAC
output.icecast(
  %ogg(%flac(samplerate=48000, channels=2, compression=8, bits_per_sample=16)),
  host=icecast_host,
  port=icecast_port,
  user=icecast_user,
  password=icecast_pass,
  mount=mount_point,
  radio
)

The resulting new stream can be played back in

  • MusicBee, which detects title and artist, not cover url
  • vlc, which detects title, artist and cover url (no display)
  • foobar2000, which detects title and artist, not cover url
  • mpc-hc, which doesn’t seem to detect any of the tags
  • mpv, which only detects the title

Symfonium uses none of the embedded tags, but they are detected in the logs:

2025-03-31 03:15:05.701 Verbose/TranscoderManager: FFProbe [790ms]: {"streams":[{"index":0,"codec_name":"flac","codec_long_name":"unknown","codec_type":"audio","codec_tag_string":"[0][0][0][0]","codec_tag":"0x0000","sample_fmt":"s16","sample_rate":"48000","channels":2,"channel_layout":"stereo","bits_per_sample":0,"initial_padding":0,"r_frame_rate":"0\/0","avg_frame_rate":"0\/0","time_base":"1\/48000","start_pts":0,"start_time":"0.000000","bits_per_raw_sample":"16","extradata_size":34,"disposition":{"default":0,"dub":0,"original":0,"comment":0,"lyrics":0,"karaoke":0,"forced":0,"hearing_impaired":0,"visual_impaired":0,"clean_effects":0,"attached_pic":0,"timed_thumbnails":0,"captions":0,"descriptions":0,"metadata":0,"dependent":0,"still_image":0},"tags":{"coverart":"https:\/\/ichef.bbci.co.uk\/images\/ic\/raw\/p01bqmgz.jpg","artist":"The Seperate","title":"This Night Has Opened My Eyes (feat. Joan As Police Woman)"}}],"format":{"filename":"https:\/\/192.168.1.53\/bbc6music.ogg","nb_streams":1,"nb_programs":0,"format_name":"ogg","start_time":"0.000000","probe_score":100,"tags":{"icy-name":"\/bbc6music.ogg","icy-pub":"1"}}}

this part of the line, to be exact:

"tags":{"coverart":"https:\/\/ichef.bbci.co.uk\/images\/ic\/raw\/p01bqmgz.jpg","artist":"The Seperate","title":"This Night Has Opened My Eyes (feat. Joan As Police Woman)"}

Screenshots:

Musicbee metadata:


VLC metadata:


Foobar2000 metadata:
grafik
mpv metadata:

Symfonium sadly displays none of the metadata (ignore the dinosaur):


And it aborts playback in under 30s each time I start it. Considering that the metadata update happens every 30s, I’d guess that Symfonium doesn’t like the changing metadata.

It would be really cool if Symfonium could utilize the metadata I added to the icecast stream via liquidsoup (at least title and artist, I don’t know how hard updating the current artwork from an url to the image would be) and also if it could play the stream without crashing within 30s.
(I was tempted to put a sun emoji here).

I have no way and no time to try to repro such kind of hacks that generates things not supported by Google libraries :wink:

Does Poweramp not rely on these Google libraries?
I just installed it to see if it’s a general Android issue or an issue with my specific stream but it neither crashes within 30s nor has it a problem with the embedded metadata except for the fact that it only updates title/artist when I pause and resume playback.

VLC for Android also has no issue playing my stream and updates the metadata during playback.

foobar2000 on Android also has no issue playing my stream and updates the metadata during playback.

This makes me think that so far, Symfonium only supports the ICY metadata which has several drawbacks as it’s just one string that can either be the title, the artist or a concatenation of the two. Embedding the tags in the actual audio chunks like it’s done in my stream is superior to this method as it allows discrete tags and more information (if the API I use included the album title for example, I could also add that as a tag).

It’s your choice ofc but I don’t think there’s anything exotic or non-standard about my stream. You could create a very basic stream by using local audio files as the source and the metadata of each track would be embedded and transmitted in the same way by default.

All the hacky stuff happens before the stream is generated.

I can give you access to my stream if you do decide to tackle this issue.