Pseudo Streaming MP4’s with PHP

Last Update: 12/12/2015 11:16AM EST

So I’ve been working on a video hosting project for most of the year and ran into issues while trying to push all storage files thru a PHP script. For proper tracking of embedded videos this was/is a must. If you’re having this problem and can’t find a solution, maybe this will help…

The below code will pseudo stream any MP4 file on any browser (including iPads/tablets) with all functionality working properly (seek forward, seek backwards and displays correct time). This code has also been tested with Longtail JWPlayer 6 and HTML5 players.

If you’re interested in an advanced version of this script, I currently have an unreleased version which includes automatic rate limiting (buffer rate). I’m also playing with other ideas to further enhance it. If you have any suggestions or questions either contact us or leave a comment at the bottom.

I have added the script to GITHub so everyone can contribute to ehance this code. Please feel free to contact me if you have any questions. https://github.com/tuxxin/MP4Streaming






Code:

<?php

$file = 'video360p.mp4';
$fp = @fopen($file, 'rb');

$size   = filesize($file); // File size
$length = $size;           // Content length
$start  = 0;               // Start byte
$end    = $size - 1;       // End byte

header('Content-type: video/mp4');
//header("Accept-Ranges: 0-$length");
header("Accept-Ranges: bytes");
if (isset($_SERVER['HTTP_RANGE'])) {

    $c_start = $start;
    $c_end   = $end;

    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    if ($range == '-') {
        $c_start = $size - substr($range, 1);
    }else{
        $range  = explode('-', $range);
        $c_start = $range[0];
        $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
    }
    $c_end = ($c_end > $end) ? $end : $c_end;
    if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    $start  = $c_start;
    $end    = $c_end;
    $length = $end - $start + 1;
    fseek($fp, $start);
    header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);


$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {

    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    flush();
}

fclose($fp);
exit();
?>


PHP Stream

HTML5 Player


JWPlayer

Comment Area

56 comments on “Pseudo Streaming MP4’s with PHP
  1. Josh says:

    This helped me greatly, thank you!

  2. Daniel J. Daniel J. says:

    You’re very welcome 🙂

    • Frank says:

      Hey! I just found this today. Can you go through the code and explain what is happening? That would be great!

      • Daniel J. Daniel J. says:

        Without going over the code and adding notes, anything that has to with the “range” variable is code to allow pseudo streaming which also relates to proper time displays. If you try to push the MP4 through the header without the pseudo streaming code, you will need to download the entire video before playing starts and your total file time will not display correctly.

  3. MK says:

    You, Sir, are great. Thx!

    • Daniel J. Daniel J. says:

      You’re very welcome! I started working on a newer version that so far includes mode changes and automatic rate limiting. I also started working with playing multiple videos (for advertisements at the stream level).

  4. Jeff says:

    Very awesome. Thanks for sharing! 🙂

  5. Osmoz says:

    Hi,

    Thanks for sharing ! Do you plan to share your new piece of code ?

    Thanks !!

  6. krish says:

    Hi, Can this script be used to get a live rtsp feed from a camera and stream it?

    Thx

  7. Toan Tran says:

    Hi Daniel,
    Thank you for sharing this. I, too, am trying to stream/host MP4 using PHP. Although this script seemed to have worked for others, I could not get it to work for the life of me. I have created a test script at
    http://clsasset.coastlinelive.com/apps/video/mp4/index.php
    Can you point me in the right direction?

    Much appreciated.

    -Toan

    • Daniel J. Daniel J. says:

      Try removing the @ on fopen and comment out the header lines (just dump the raw file). I think you’re getting a permission error of some sort.

      • Toan Tran says:

        Thanks for the reply, Daniel. I tried that, and all hell broke loose. Going straight to http://clsasset.coastlinelive.com/apps/video/mp4/stream.php on Chrome shows junk on page (I’m guessing this happens because I didn’t specify the Content-Type header). IE complains that file is corrupt, and Firefox just crashes.

        Putting just the Content-Type header back in makes the script behave like before: all browsers complain about corrupt files.

        I just checked all the permissions, and they are as open as can be. I also have another test script that uses Readfile, and it seems to work fine

        I’m especially interested in your script for its ability to stream the file incrementally without having to load the entire clip into memory.

        If nothing else, I’ve discovered a way to crash a Firefox client 🙂

      • Toan Tran says:

        Hi Daniel,
        I ended up using a header location redirect until I have this figured out. You may be onto something about permission, too. I know I’m missing something really obvious and will kick myself when I figure it out.

        Thanks again for the help.

        • Daniel J. Daniel J. says:

          Yeah the garbage output was the raw output of the mp4 file. One problem I ran into testing this script a lot if caching. You’ll need to constantly change the streaming file during testing.

          However, I see your stream link is now working. GJ.

          • Toan Tran says:

            I added two lines at the top to turn off buffer and set timeout to 0, and things started working. One behavior I noticed was that if you try to seek beyond the buffer line, the HTML5 player will put you back at the end of the buffer line.

            This is fine for my needs. From the different tests, and except for the direct link, yours seems to be the only way to preview the total play time in the player.

            Thanks again for sharing this code, and for the pointers.
            – Toan

  8. Lee Ralls says:

    Have you tested this with videos that are stored outside of web root? I’ve adopted your script in an attempt to solve some issues that I’ve been having, but the same problems arise: the website becomes unresponsive (per browser, meaning its responsive if i open up an alternate browser). This problem only occurs when the video is first loading and you make any other http request: press ‘back’ or click a link. Any ideas?

    • Daniel J. Daniel J. says:

      Yes I have. I have a version of this running on a video streaming server with files multiple levels deep. What player are you using?

      • Lee Ralls says:

        Using videojs. This application is an online school, the videos are also multiple levels deep. I’m hoping that this is simply the player that is causing the problem and not the server, but I get no errors. What would you recommend?

  9. chris says:

    Ive been using this script for a few days with very few issues. It seems that mainly on android, the browser will continously buffer then eventually hang up. But, if I load the same file directly into the player, I have no issues.

    • Daniel J. Daniel J. says:

      Chris,

      It’s been quite some time since I’ve tested it on an Android device. Does the video load while still buffering or does it not play at all? Have you tried HTML5 as the player?

  10. chris says:

    I am using HTML 5 as JWPlayer combined with the script doesnt seem to work on any of my devices. It usually says the format is unsupported. On Android 4.4, using chrome, after I click play a frame or two will play then the player stops. The browser progress bar has stopped moving but it seems to be trying.

  11. Cristian B says:

    Great script, helped me out of a jam

  12. Nenad says:

    Thanks for this!

  13. Samuel Cheung says:

    This code works for most mp4 videos, but not when the mp4 file size is too large. You know, we could only output large files using readfile() function. Any suggestion to output large mp4 videos?

  14. Hello World says:

    This code works fine, yet not when I try to retrieve values in $_SESSION and $_COOKIE. Is there any solution to this?
    I need to access cookies since I need to authenticate the user to determine if video should be output.

  15. Jacob C. says:

    This is awesome, it works great. There is a problem though. If I have a file the is a few gigabytes then I get an error. It says “Requested Range Not Satisfiable”. Do you know what causes this and how I can fix it. Thank you!

    • Daniel J. Daniel J. says:

      I would think this would be due to a limitation in your PHP settings. Tweak your PHP.ini.

      • Jacob C. says:

        I think I have figured out the problem. It has to do with line 6. The filesize function, this is from the php manual: Note: Because PHP’s integer type is signed and many platforms use 32bit integers, some filesystem functions may return unexpected results for files which are larger than 2GB.

  16. Mac says:

    Doesn’t work on iphone/ipad with remote file. Any solution for that?

    • Mac says:

      for remote file i use CURL to get ize instead of php filesize. I tried IOS simulator to debug. Headers of file looks fine. Content-length, byte-range looks fine but video won’t play on iPhone/iPad.

      I get this error: “Failed to load resource: Plug-in handled load”

      File is encoded properly with H.264, Direct access of mp4 file work on iPhone/iPad but with script it doesn’t work.

      When i upload same file locally and try to stream with this script it works fine.

      Also i tried removing headers just to see if video data is coming and i was able to see complete video data.

      Any thoughts about it?

      • Daniel J. Daniel J. says:

        Using this on a remote stream/file is something I haven’t tried. Off the top of my head, I would think the headers would be doubled up. The server you’re pulling the file from sends it’s own headers which likely overwrites the scripts headers and stops the script from working properly.

        I’d suggest trying header_remove() after your curl request, but before the script outputs.

        Let me know how it works out for you.

        • Mac says:

          Thanks for your response. i just tried remove_headers() still no luck.

          No headers were sent i just checked with this:

          if (headers_sent()) {
          exit;
          }

  17. Mark F. says:

    Great work! As an aside this also works with MP3 if you change the content-type to audio/mp3.

  18. Keo Strife says:

    Hi Daniel,

    It works great, but in Chrome, i can’t play 2 videos at once in 2 tabs… or even when im playing one video and refresh the tab, it will not play right away.

    When i inspect the header of the 2nd request, it says “[Caution sign] Provisional headers are shown”
    Also when i inspect the header of the 1st request, in the Timing tab it says “CAUTION: request is not finished yet!”

    it’s only in Chrome so far, do you have any idea what is happening? 🙁

    I do use Cookie and Session to authenticate the user before serving the mp4 to client

    • Matt Palermo says:

      Are you closing the Session after you are done with it? If you open the session and don’t close it before the streaming is done, the session will remain open and locked, which will prevent other PHP scripts from using the session until it completes. Try using session_write_close() before starting the streaming and see if that frees it up.

    • Matt Palermo says:

      Any idea how I would add a watermark to the video stream? I’d like to add a dynamic watermark with the users name to the video stream to help prevent unauthorized sharing of the video. How can I transcode the video with a watermark added to it during streaming?

  19. Daniel J. Daniel J. says:

    Keo, this is something I’ve never tried of heard of anyone else trying. I’m sorry but I currently can’t think of any cause.

  20. Manuel Zapata says:

    Wow, this code worked great. I was looking for something like this to keep more control of who accesses our videos.

    The script works without issues pulling the videos a few levels deep of the root. Combined with jwPlayer it works very nice.

  21. Giorgio B. says:

    That’s really great.
    I use use it in combination mith JWplayer and works like a charm with any browser.
    Thank you for sharing it.

  22. Jon S says:

    This streamed my hundred meg video. Great work!!

  23. Dan says:

    Nice code! It seems to work well with your penguin video. I am amiss on how to point an HTML5 video tag to it, can you toss one up?
    Thanks!

  24. Kskets says:

    Thank you very much for this script !

1 Pings/Trackbacks for "Pseudo Streaming MP4’s with PHP"
  1. […] this reason I use PHP to interact with Stripe to check for user’s subscription, and than used PHP script to serve MP4 to […]

Comment

Leave a Reply to Matt Palermo Cancel reply

Your email address will not be published.

Time limit is exhausted. Please reload the CAPTCHA.

Loading Facebook Comments ...