10

I did this PHP script


    $file_name = 'sample.mp3';

    header('Content-Type: audio/mpeg');

    $opts     = array('http' =>
                      array(
                          'method'           => 'GET',
                          'protocol_version' => 1.1,
                      )
    );
    $context  = stream_context_create($opts);
    $stream   = fopen($file_name, 'rb', FALSE, $context);
    $metadata = stream_get_meta_data($stream);
    $data     = stream_get_contents($stream);
    print($data);
    fclose($stream);

It could stream mp3 media successfully but I'm not able to seek the mp3 file that get played also in html5 video tags are not able to extract metadata from it, please tell me where I'm doing wrong and how to make this process work, thanks

2 Answers2

25

Here try this, supports partial downloads and seeking for any filesize, also now correctly works in chrome:

<?php 
$file_name = './sample.mp3';
stream($file_name, 'audio/mpeg');

/**
 * Stream-able file handler
 *
 * @param String $file_location
 * @param Header|String $content_type
 * @return content
 */
function stream($file, $content_type = 'application/octet-stream') {
    @error_reporting(0);

    // Make sure the files exists, otherwise we are wasting our time
    if (!file_exists($file)) {
        header("HTTP/1.1 404 Not Found");
        exit;
    }

    // Get file size
    $filesize = sprintf("%u", filesize($file));

    // Handle 'Range' header
    if(isset($_SERVER['HTTP_RANGE'])){
        $range = $_SERVER['HTTP_RANGE'];
    }elseif($apache = apache_request_headers()){
        $headers = array();
        foreach ($apache as $header => $val){
            $headers[strtolower($header)] = $val;
        }
        if(isset($headers['range'])){
            $range = $headers['range'];
        }
        else $range = FALSE;
    } else $range = FALSE;

    //Is range
    if($range){
        $partial = true;
        list($param, $range) = explode('=',$range);
        // Bad request - range unit is not 'bytes'
        if(strtolower(trim($param)) != 'bytes'){ 
            header("HTTP/1.1 400 Invalid Request");
            exit;
        }
        // Get range values
        $range = explode(',',$range);
        $range = explode('-',$range[0]); 
        // Deal with range values
        if ($range[0] === ''){
            $end = $filesize - 1;
            $start = $end - intval($range[0]);
        } else if ($range[1] === '') {
            $start = intval($range[0]);
            $end = $filesize - 1;
        }else{ 
            // Both numbers present, return specific range
            $start = intval($range[0]);
            $end = intval($range[1]);
            if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) $partial = false; // Invalid range/whole file specified, return whole file
        }
        $length = $end - $start + 1;
    }
    // No range requested
    else $partial = false; 

    // Send standard headers
    header("Content-Type: $content_type");
    header("Content-Length: " . ($partial ? $length : $filesize));
    header('Accept-Ranges: bytes');

    // send extra headers for range handling...
    if ($partial) {
        header('HTTP/1.1 206 Partial Content');
        header("Content-Range: bytes $start-$end/$filesize");
        if (!$fp = fopen($file, 'rb')) {
            header("HTTP/1.1 500 Internal Server Error");
            exit;
        }
        if ($start) fseek($fp,$start);
        while($length){
            set_time_limit(0);
            $read = ($length > 8192) ? 8192 : $length;
            $length -= $read;
            print(fread($fp,$read));
        }
        fclose($fp);
    }
    //just send the whole file
    else readfile($file);
    exit;
}
?>
John Larson
  • 1,359
  • 1
  • 8
  • 8
Lawrence Cherone
  • 46,049
  • 7
  • 62
  • 106
  • I have tried this and it works brilliantly when I copy the PHP files address into VLC. However if I add the link to a HTML5 audio tag, or JWplayer it doesn't recognize the stream. I get errors in my log stating: PHP Warning: Cannot modify header information - headers already sent. Thank you. – joshkrz Feb 01 '14 at 13:50
  • 1
    @joshkrz - You should make sure you do not send any output(whitespace, html) before setting headers, the error your getting will tell you where your outputting. – Lawrence Cherone Feb 01 '14 at 13:53
  • Apparently I am outputting on line 89 [ print($download_data_chunk); ]. The error just tells me all the lines that have headers set within your code. The strange thing is if I type a direct link to this script it shows a Google Chrome MP3 player but doesn't do anything. I don't get any errors when I do this. The script doesn't have any extra echos or prints. – joshkrz Feb 01 '14 at 14:09
  • 1
    @joshkrz the Exception lines will echo out onto the page, im not sure how your calling it, but you should wrap it in a `try{}catch(){}` to catch the exceptions. – Lawrence Cherone Feb 01 '14 at 14:16
  • I have tried this in Firefox and it works perfectly in the native MP3 player (via direct link) and my inline js players. It doesn't come across like a browser issue though. – joshkrz Feb 01 '14 at 14:20
  • 1
    let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46595/discussion-between-loz-cherone-and-joshkrz) – Lawrence Cherone Feb 01 '14 at 14:22
  • 2
    @joshkrz function is now fixed for chrome, hope it helps – Lawrence Cherone Feb 01 '14 at 16:17
  • Thanks @LozCherone. I've been trying to get this to work with an audio tag and serving the mp3 from a PHP script, see: http://stackoverflow.com/questions/22213180/safari-doesnt-show-duration-of-mp3-served-from-php-correctly Using this function seems to work locally even with Safari, but on the remote VPS it still doesn't work. It seems that on the VPS only the first bytes=0-1 request comes through and there is no followup request to get the actual audio data. Locally that does happen so I'm really at a loss here. Any ideas? – Ruben Mar 09 '14 at 23:50
  • 2
    @Loz Cherone Content-Length should be the length you send. That is not always filesize($file). – B.F. Nov 02 '14 at 08:41
  • It'd be nice to credit the person [you copied your answer](http://stackoverflow.com/a/4451376/84631) from. – Mischa Apr 30 '15 at 13:58
  • Yes, this is genius! Thank you – Leon Sep 30 '16 at 11:53
  • 2
    Actually (made a suggested edit but now I can't undo), I think the Content-Length should be more like: `header("Content-Length: " . ($range ? ($end - $start + 1) : $filesize));`. – Agamemnus Jun 11 '17 at 22:11
  • Thank so much for this answer - life saver! One thing: I had to add ```header("Content-Length: " . ( $end - $start ) );``` in the if($partial) block, to prevent chrome from complaining about a content length mismatch ```net::ERR_CONTENT_LENGTH_MISMATCH``` -- edit: as @B.F and @Agamemnus already pointed out, sorry! – Soft Bullets Aug 02 '17 at 15:21
  • Thank you very much. It works in all browsers except Mac Safari and iPhone Safari. After applying the suggestion by @Agamemnus it now works for Safari. – Wayne Liu Nov 19 '17 at 04:47
  • Noticed an issue, though. When the MP3 play-time is very long (e.g. 40 minutes or so), it would take very long to launch the player. Still looking for solutions. – Wayne Liu Dec 02 '17 at 10:37
  • This does not work for the latest Chrome. I'm using Version 64.0.3282.186 (Official Build) (64-bit). Worked for previous versions of Chrome. – Gregory R. Mar 04 '18 at 04:25
  • This works great in all desktop browsers but fails on ios Safari - audio doesn't play. Anybody have any suggestions? – spice Jun 05 '21 at 23:59
  • @spice I checked the code on IOS safari & IOS chrome, it works without problem – Kranchi May 07 '23 at 11:35
0

This should hopefully fix your problem, follow this method.

What you need to do is use readfile() and set file_exists() so that you can tell you're opening the correct file.

$extension = "mp3";
$mime_type = "audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3";

if(file_exists($file_name)){
    header('Content-type: ' . $mime_type);
    header('Content-length: ' . filesize($file_name));
    header('Content-Disposition: filename="' . $file_name);
    header('X-Pad: avoid browser bug');
    header('Cache-Control: no-cache');
    readfile($file_name);
}

Reference

ctf0
  • 6,991
  • 5
  • 37
  • 46
jay
  • 916
  • 1
  • 5
  • 13