0

I'm trying to extract icecast metadata from streams. I have code that works for some streams and not for others. The issue is that some streams don't return the icymetaint value and that's where the code gets lost.

I can't get the icymetaint header from this stream: http://radio.hbr1.com:19800/tronic.ogg

But when I put it in VLC media player it shows the meta just fine. So what exactly am I missing here? What other ways are there for an icecast stream to transmit metdata? The stream version is Icecast 2.3.3

This is code inside a class to retrieve the metadata and headers:

public function GetDataFromStream($parsedUrl)
{
    $returnData = array();
    $addr = $parsedUrl['host'];

    $addr = gethostbyname($addr);

    $sock = fsockopen($addr, $parsedUrl['port'], $errno, $errstr, 5);
    $path = isset($parsedUrl['path'])?$parsedUrl['path']:'/';

    if ($sock)
    {
        $request = 'GET '. $path .' HTTP/1.0' . CRLF .
            'Host: ' . $parsedUrl['host'] . CRLF .
            'Connection: Close' . CRLF .
            'User-Agent: ' . $this->useragent . CRLF .
            'Accept: */*' . CRLF .
            'icy-metadata: 1'.CRLF.
            'icy-prebuffer: 65536'.CRLF.
            (isset($parsedUrl['user']) ? 'Authorization: Basic ' .
            base64_encode($parsedUrl['user'] . ':' . $parsedUrl['pass']) . CRLF : '').
            'X-TipOfTheDay: Winamp "Classic" rulez all of them.' . CRLF . CRLF;

        if (fwrite($sock, $request))
        {
            $theaders = $line = '';

            while (!feof($sock))
            {
                $line = fgets($sock, 4096);

                if('' == trim($line))
                    break;
                $theaders .= $line;
            }

            $theaders = explode(CRLF, $theaders);

            foreach ($theaders as $header)
            {
                $t = explode(':', $header);


                if (isset($t[0]) && trim($t[0]) != '')
                {
                    $name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
                    array_shift($t);
                    $value = trim(implode(':', $t));

                    if ($value != '')
                    {
                        if (is_numeric($value))
                            $this->headers[$name] = (int)$value;
                        else
                            $this->headers[$name] = $value;
                    }
                }
            }

            if (isset($this->headers['icymetaint']))
            {
                $metainterval = $this->headers['icymetaint'];
                $intervals = 0;
                $metadata = '';

                while(1)
                {
                    $data = '';

                    while(!feof($sock))
                    {
                        $data .= fgetc($sock);

                        if (strlen($data) >= $metainterval)
                            break;
                    }

                    $len = join(unpack('c', fgetc($sock))) * 16;

                    if ($len > 0)
                    {
                        $metadata = str_replace("\0", '', fread($sock, $len));
                        break;
                    }
                    else
                    {
                        $intervals++;
                        if ($intervals > 100) break;
                    }
                }

                $metarr = explode(';', $metadata);

                foreach ($metarr as $meta)
                {
                    $t = explode('=', $meta);

                    if (isset($t[0]) && trim($t[0]) != '')
                    {
                        $name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));

                        array_shift($t);

                        $value = trim(implode('=', $t));

                        if (substr($value, 0, 1) == '"' || substr($value, 0, 1) == "'")
                            $value = substr($value, 1);

                        if (substr($value, -1) == '"' || substr($value, -1) == "'")
                            $value = substr($value, 0, -1);

                        if ($value != '')
                        {
                            $tmp = &$this->metadata;
                            $tmp[$name] = $value;
                        }
                    }
                }
                $this->valid = true;
            }
            else
            {
                $this->valid = false;
            }

            fclose($sock);
        }
        else
            echo 'unable to write.';
    }
    else
        //echo 'no socket '.$errno.' - '.$errstr.'.';
        ;

}
Not Amused
  • 942
  • 2
  • 10
  • 28

2 Answers2

2

You can use .xspf mountpoint extension, get XML and parse it:

<?php
$stream_url = "http://radio.hbr1.com:19800/tronic.ogg";
$xspf_url = $stream_url . ".xspf";
$xml = file_get_contents($xspf_url);
if($xml){
    $data = simplexml_load_string($xml);
    // Track artist
    print $data->trackList->track->creator;
    // Track title
    print $data->trackList->track->title;
}
?>

Here is how .xspf data looks like (I use lynx to read the URL content):

$ lynx -mime_header http://radio.hbr1.com:19800/tronic.ogg.xspf
HTTP/1.0 200 OK
Content-Type: application/xspf+xml
Content-Length: 615

<?xml version="1.0" encoding="UTF-8"?>
<playlist xmlns="http://xspf.org/ns/0/" version="1">
  <title/>
  <creator/>
  <trackList>
    <track>
      <location>http://radio.hbr1.com:19800/tronic.ogg</location>
      <creator>Res Q</creator>
      <title>Fakesleep (2012)</title>
      <annotation>Stream Title: HBR1 - Tronic Lounge
Stream Description: Music on Futurenet
Content Type:application/ogg
Bitrate: Quality 0,00
Current Listeners: 28
Peak Listeners: 45
Stream Genre: Tech House, Progressive House, Electro, Minimal</annotation>
      <info>http://www.hbr1.com</info>
    </track>
  </trackList>
</playlist>

As you can see /playlist/trackList/track/title XML node is your song title, /playlist/trackList/track/creator is usually an artist.

Alex Paramonov
  • 2,630
  • 2
  • 23
  • 27
  • I checked .xspf and it does list number of listeners, listeners peak, quality. It doesn't list the most important thing for me though, that is the song title. Am I missing something? – Not Amused Oct 25 '16 at 16:25
  • I realized now that the streams that support the json version 2.4 and above are the ones that don't return the title in the .xspf file. Others do. Thank you very much. Marked as right answer. – Not Amused Oct 25 '16 at 21:39
  • Do you mean Icecast 2.4? Strange, my solution should work with 2.4 too, can you send me a link to Icecast 2.4 stream where title is missing in `.xspf`? – Alex Paramonov Oct 26 '16 at 02:01
0

That's because you are only trying to parse the braindead ancient metadata slipstreaming introduced by Nullsoft in Shoutcast.

Proper streams use a container (e.g. Ogg or WebM) instead of throwing raw data out.

Newer Icecast servers offer a JSON API (Version 2.4.1 and above). This is more useful than pulling in a whole stream just for the metadata.

If you are decoding the stream anyway, then you should look into proper libraries for parsing streams, libogg, libopus, libvorbis come to mind.

TBR
  • 2,790
  • 1
  • 12
  • 22
  • Thanks for your answer. The problem is that I have no control over what version the stream will be so I need to be able to parse them all. I tried goolging Ogg and WebM, libogg, libopus, libvorbis with php keyword but I didn't find anything useful on parsing the sterams with php. – Not Amused Oct 22 '16 at 05:40