Skip to content

Instantly share code, notes, and snippets.

@ranacseruet
Last active February 19, 2024 17:10
Show Gist options
  • Save ranacseruet/9826293 to your computer and use it in GitHub Desktop.
Save ranacseruet/9826293 to your computer and use it in GitHub Desktop.
PHP VideoStream class for HTML5 video streaming
<?php
/**
* Description of VideoStream
*
* @author Rana
* @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
*/
class VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath)
{
$this->path = $filePath;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: video/mp4");
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->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 $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
@marlon33
Copy link

It's awesome, can you share your code??

@David263
Copy link

David263 commented Jul 15, 2022

It's still under development. It works great, but fails on Safari/MacOS. I am having great difficulty figuring out how to debug it on Safari, which seems to not have Dev Tools. It is an app that asks the user for a password, then provides a list of video files as buttons. Clicking a button plays the file, with repositioning, using the default browser video player. The entire app is a bit lengthy for simply posting, but eventually I can release it as a repository.

@marlon33
Copy link

Understand, if you need some help, just put here

@bondan20
Copy link

bondan20 commented Sep 1, 2022

for mp4 work sir
how to play m3u8 Sir?

@marlon33
Copy link

marlon33 commented Sep 2, 2022

for mp4 work sir how to play m3u8 Sir?

It's note possible, becouse the file m3u8 is a stream file

@bondan20
Copy link

bondan20 commented Sep 8, 2022

for mp4 work sir how to play m3u8 Sir?

It's note possible, becouse the file m3u8 is a stream file

not work sir,
please create streaming for m3u8 sir :D heheheh

@himadrisaha89
Copy link

Working fine for me for small size video file but for big size file I am getting net::ERR_CONTENT_LENGTH_MISMATCH 206 (Partial Content) in chrome console and of course the status is failed in chrome network tab.
Any Help !!!

@David263
Copy link

David263 commented Sep 8, 2022

himadrisaha89, this usually means that the software that is sending the streaming file is either calculating or formatting the size information that it sends to the client player incorrectly. If you look at the network information in a browser developer tool, you might be able to see the problem.

@michalcz17
Copy link

Is it possible for the controls not to be displayed on that page? Or at least not be able to download the video? or do you need a script for that?

@micilini
Copy link

micilini commented Nov 16, 2022

This code need UPDATE, he cannot read EXTERNAL URL VIDEO, only local videos, for solve this problem I create a new package that works with laravel, check it out: https://github.com/micilini/video-stream.

My Package can handle local and external video files 👯

@WordpressArtist
Copy link

I got an error please help

filesize(): stat failed for http://laraveltest.test/ElephantsDream.mp4

got error here. from class VideoStream
$this->size = filesize($this->path);

where code

$a = 'http://laraveltest.test/ElephantsDream.mp4'; // public folder video
$stream = new VideoStream($a);
return response()->stream(function() use ($stream) {
$stream->start();
});

Any help please using laravel 9

@carcinocron
Copy link

trying to access a file using http:// does not look valid to me, probably you want something like one of these:

$a = storage_path('ElephantsDream.mp4');
$a = public_path('ElephantsDream.mp4');
$a = asset('ElephantsDream.mp4');

depending on where the file is physically in your laravel directory.

@WordpressArtist
Copy link

@carcinocron thanks so external url not supported right ?

@jackbaron
Copy link

Is there a way to do this same process but with AJAX?

@vincentckk
Copy link

Here is my code. For sure require to check URL and File. Just assume file path / URL is correct.
// Check starting string
if (substr($this->path, 0, 3) == 'htt' || substr($this->path, 0, 3) == 'ftp') { /* External URL - allow_url_fopen=On /
$headers = get_headers($this->path, 1);
$this->size = $headers["Content-Length"];
}else{ /
Internal file */
$this->size = filesize($this->path);
}
:
//fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
// Use stream_get_contents instead of fseek
echo stream_get_contents($this->stream, -1, $this->start);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment