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
This helped me greatly, thank you!
You’re very welcome 🙂
Hey! I just found this today. Can you go through the code and explain what is happening? That would be great!
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.
You, Sir, are great. Thx!
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).
Hello! How far along are you in the direction with playing multiple videos (for advertisements at the stream level)?
I played around with it for a bit last year, but didn’t get too far. I haven’t had time to continue development due to other projects.
Very awesome. Thanks for sharing! 🙂
Hi,
Thanks for sharing ! Do you plan to share your new piece of code ?
Thanks !!
I haven’t fully decided yet. A bug was found when seeking out of the buffer, which I’m also fixing.
Hi, Can this script be used to get a live rtsp feed from a camera and stream it?
Thx
This exact script would not work with RTSP, however it could be altered to work. Feel free to contact me if you’d like to discuss it further.
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
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.
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 🙂
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.
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.
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
Yeah, that bug was recently discovered. I’ll be fixing it when I get a bit more free time.
Thanks for your support.
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?
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?
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?
No the script shouldn’t be causing this issue. Click Tux in the bottom right corner and we’ll chat.
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.
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?
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.
With JWPlayer you need to add the line “type: ‘mp4’,” without the double quotes. This will fix your issue.
Great script, helped me out of a jam
Thanks for this!
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?
The largest file I’ve tested on is 700mb’s and it seemed to work fine.
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.
I have a version of the script working with sessions, what issues are you having with it?
That will be great if I can get the code version working with sessions.
As long as I can both output video and access values in session variable, that will be fine.
What error are you getting?
There are no errors but it is just that the video could not be played once session is retrieved.
Check your logs for errors or disable the headers and file output of the streamer to see what’s stopping it. It sounds like something is outputting before the headers.
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!
I would think this would be due to a limitation in your PHP settings. Tweak your PHP.ini.
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.
Doesn’t work on iphone/ipad with remote file. Any solution for that?
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?
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.
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;
}
Great work! As an aside this also works with MP3 if you change the content-type to audio/mp3.
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
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.
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?
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.
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.
It does work very well with JWPlayer. I’m glad it helped you out 🙂
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.
This streamed my hundred meg video. Great work!!
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!
Thank you very much for this script !