7

I'm trying to setup a page where several (private) streams can be listened from. Unfortunately I'm not able to get it running. I tried Using php to opening live audio stream on android already, but for some reason the browser get stuck when loading the script.

See below script with an example of a working host (see http://icecast.omroep.nl/radio4-bb-mp3)

Could someone please enlighten me.

Tnx in advance!

    $host = "icecast.omroep.nl";
    $port = 80;
    $sub = "/radio4-bb-mp3";


    $sock = fsockopen($host,$port, $errno, $errstr, 30);
    if (!$sock){
        throw new Exception("$errstr ($errno)");
    }

    header("Content-type: audio/mpeg");
    header("Connection: close");

    fputs($sock, "GET $sub HTTP/1\r\n");
    fputs($sock, "Host: $host \r\n");
    fputs($sock, "Accept: */*\r\n");
    fputs($sock, "Icy-MetaData:1\r\n");
    fputs($sock, "Connection: close\r\n\r\n");

    fpassthru($sock);
    fclose($sock);
Brad
  • 159,648
  • 54
  • 349
  • 530
Kootsj
  • 431
  • 3
  • 12
  • 1
    Why would you want to do this? There's significant extra overhead by running your stream through PHP. Why not just redirect to the stream? – Brad Mar 20 '18 at 15:00
  • @Brad The stream is changing every day so I want to build some logic around it to make sure the user is allowed to use the stream. I want to configure this on my Sonos, but I don't want to change the URL every day – Kootsj Mar 20 '18 at 18:50
  • @Brad I could indeed redirect the URL, but does it really take that much overhead? The amount of streamers is not much, so I wouldn't mind if its taking much from the server – Kootsj Mar 20 '18 at 19:05
  • 1
    Why add complexity and load when there's no reason to? Just use a 302 redirect. Also, if you were to proxy the original, you should use a proper HTTP client. Your code here is not compliant at all with the spec, and you'll run into servers that it won't work on. – Brad Mar 20 '18 at 19:19

1 Answers1

1

Following comments the solution you are looking is:

<?php

$host = "icecast.omroep.nl";
$sub = "/radio4-bb-mp3";
header("Location:  http://{$host}{$sub}");

Now I will explain what was wrong with your code

You have a problem with the headers. You are adding your own headers and the remote headers as part of the body.

icecast.omroep.nl headers

HTTP/1.0 200 OK
Content-Type: audio/mpeg
Date: Sat, 24 Mar 2018 16:01:23 GMT
icy-br:192
ice-audio-info: samplerate=48000;channels=2;bitrate=192
icy-br:192
icy-genre:Classical
icy-metadata:1
icy-name:NPO Radio4
icy-pub:0
icy-url:http://www.radio4.nl
Server: Icecast 2.4.0-kh8
Cache-Control: no-cache, no-store
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type
Access-Control-Allow-Methods: GET, OPTIONS, HEAD
Connection: Close
Expires: Mon, 26 Jul 1997 05:00:00 GMT
icy-metaint:16000

Given your script index.php

<?php

$host = "icecast.omroep.nl";
$port = 80;
$sub = "/radio4-bb-mp3";


$sock = fsockopen($host,$port, $errno, $errstr, 30);
if (!$sock){
    throw new Exception("$errstr ($errno)");
}

fputs($sock, "GET $sub HTTP/1\r\n");
fputs($sock, "Host: $host \r\n");
fputs($sock, "Accept: */*\r\n");
fputs($sock, "Icy-MetaData:1\r\n");
fputs($sock, "Connection: close\r\n\r\n");

fpassthru($sock);
fclose($sock);

request.txt

GET /
[Blank line]

Serving your script

$ php -S 0.0.0.0:8000 index.php

Your script response:

$ (nc 127.0.0.1 8000 < request.txt) | head -n 27

HTTP/0.9 200 OK
Date: Sat, 24 Mar 2018 16:01:23 +0000
Connection: close
X-Powered-By: PHP/7.1.14
Content-type: text/html; charset=UTF-8

HTTP/1.0 200 OK
Content-Type: audio/mpeg
Date: Sat, 24 Mar 2018 16:01:23 GMT
icy-br:192
ice-audio-info: samplerate=48000;channels=2;bitrate=192
icy-br:192
icy-genre:Classical
icy-metadata:1
icy-name:NPO Radio4
icy-pub:0
icy-url:http://www.radio4.nl
Server: Icecast 2.4.0-kh8
Cache-Control: no-cache, no-store
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type
Access-Control-Allow-Methods: GET, OPTIONS, HEAD
Connection: Close
Expires: Mon, 26 Jul 1997 05:00:00 GMT
icy-metaint:16000

PHP is adding his own headers.

You need to process the headers received from http://icecast.omroep.nl/radio4-bb-mp3 and return them using the method header() and then you can do the fpassthru().

HTTP separate the headers from the body with a new line: https://www.rfc-editor.org/rfc/rfc2616#section-6

[header]
CRLF
[body]

So it should be easy to parse line by line and calling header() until CRLF (empty line) is found and then trigger the fpassthru().

Community
  • 1
  • 1
albert
  • 4,290
  • 1
  • 21
  • 42
  • 1
    This is a bad solution to the problem. Use a proper HTTP client. Manually parsing the response from the upstream server is going to lead to trouble. Already, this code doesn't do any handling at all of the response status, let alone following redirects and such. There is absolutely no reason to use a pure socket client to connect to an Icecast server. cURL could be used. (Although, a better solution altogether is just to redirect to the stream in the first place, as I said in my comments.) – Brad Mar 25 '18 at 03:17
  • 1
    I'm just explaining the issue @koots is facing with his solution, I think all of us can learn from it. There is better ways to do it I agree but his solution requires a deep understanding of the HTTP protocol. – albert Mar 25 '18 at 03:21
  • 1
    No, it doesn't. In fact, trying to go beyond surface level *is* the whole problem here. There is no reason to re-invent an HTTP client, and if that weren't done in the first place, there'd be no reason to figure out how to determine the response headers from the response body. Your explanation of what's happening is fine, but the solution you provide is insufficient and will only lead to more problems after it is implemented. This mistake is made often with Icecast clients... a proper reference answer should have the correct solution. – Brad Mar 25 '18 at 03:25
  • 1
    If @koots wants to act as a "proxy", Should I recommend to install guzzle? His solution is quite close of what he needs I agree that having a Guzzle client would be more ideal but it can be done without. – albert Mar 25 '18 at 03:31
  • 1
    A minimum implementation would just be `readfile()`... again though, it's the wrong solution. The right solution is just to redirect to the server. If proxying were required, the right solution there is to let another Icecast instance handle it so the stream can be buffered locally. – Brad Mar 25 '18 at 03:35
  • albert and @Brad, tnx both for your input. I now know building a "proxy" isn't a good solution. I'll mark this answer as accepted, although I'll use Brad's first suggestion - to use a redirect. – Kootsj Mar 26 '18 at 06:07