Web Based Streaming Media Server - php

I am trying to build a Media Server that will be used only on my own personal network. No outside access will be available, except for those devices that are connected via LAN or WiFi. I have ripped my entire DVD collection some SD, and some HD as to be able to play it via PS3, DVD Players, etc, without having to dig the disc out every time someone wants to watch something. Because I want to limit access to certain parts of the media, VLC, TVersity, Plex, etc don't work, and I want to custom tailor it with my own design without having to go to any outside source for processing and want it to be web based so it can be viewable in Safari or Chrome. All files are in .mp4 format, so transcoding is not necessary, or at least I don't think it is.
I have a working page that works on the machine that the XAMPP server is running on. The info is provided via a MySQL database and using PHP and HTML5 to play the video. I will be building a dedicated Linux server very soon, just trying to get it working. Files under 2GB work just fine on the same machine as XAMPP is installed (but NOT on devices that are connected via wifi or wire), but anything over 2GB just continuously say loading on all devices. Trying to view on iPad Air, iPhone6 but doesn't work.
The Streaming Code:
<?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();
}
}
?>
The HTML Code:
<video width="320" height="240" controls><?php
$stream = new VideoStream($row_rsTitles['Category'].'/'.$row_rsTitles['Title'].'/'.$row_rsTitles['Filename']);
$stream->start();
?></video>
Any help would be appreciated. Many of hours, and trying a hundred different options with no success.

Related

Why VideoStream class not open a external video URL?

I'm using a class for video stream:
<?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();
}
}
I can call this class by doing:
include_once 'videostream.php';
$stream = new VideoStream("video.mp4");
$stream->start();
This code works with local videos, but the problem is: When I try to open a external video like this:
$stream = new VideoStream("https://www.w3schools.com/html/mov_bbb.ogg");
The vĂ­deo don't open and I don't receive any error messages? Is a fopen() problem? Is a server problem? I'm using xampp v3.3.0.
When I see Network tab in Google Chrome, I receive two headers and one of them is pending.
UPDATE 1 (Partial Solution)
After performing some tests I made some modifications to the code and I was able to open the video using an external URL, follow the corrections below:
1.The video that I wanna play has HTTPS (https://www.w3schools.com/html/mov_bbb.ogg) and not HTTP format, so you must change all
headers that was HTTP/1.1 to HTTPS/1.1:
header('HTTPS/1.1 416 Requested Range Not Satisfiable');
header('HTTPS/1.1 206 Partial Content');
2.The command filesize does not work when it comes to an external URL, so we need to create a function capable of getting the size of the file we want:
function getContentLengthFromURL($url){
$arrayHeaders = get_headers($url,true);
if(isset($arrayHeaders['Content-Length'])){
if(is_numeric($arrayHeaders['Content-Length']) && $arrayHeaders['Content-Length'] > 0){
return $arrayHeaders['Content-Length'];
}
}
return null;
}
After that we need to update setHeader() function:
//$this->size = filesize($this->path);
$this->size = $this->getContentLengthFromURL($this->path);
3.We need to comment out all the lines that specify the Content-Range: and Fseek()
//header("Content-Range: bytes $this->start-$this->end/$this->size");
//fseek($this->stream, $this->start);
Amazing, now the HTML5 player is able to play the video, the only problem is that you are still unable to change the time of the video.
In this case I can't jump forward or back my video.
UPDATE 2 (THE SOLUTION)
After several attempts I finally got a solution to the code problem.
The problem was in the content-ranges and in the positioning of the headers.
By the way, I created a videoStream package using the new code, if you want an automated videoStream, just access this link.

Filestreaming to html5 in PHP without writing a file to the filesystem

I'm fairly new to PHP and definitely new to streaming video.
I have mp4 videos stored in a filestream column of my SQL SERVER, and I'd like to stream them to HTML5 video.
I've managed to use the following stream class ( http://codesamplez.com/programming/php-html5-video-streaming-tutorial ) by providing it a temporary file via 'file_put_contents($filePath, $queryresult),' but having to depend on writing and then cleaning up an intermediate file in the filesystem is problematic in the end.
Am I missing a key concept that would allow me to stream a file directly from the DB to the video "src" without having to write to a file in between?
Thanks in advance!
If you were creating temp files yourself and then deleting it. Then you can improve the process by using PHP's inbuilt
tempfile() which
Creates a temporary file with a unique name in read-write (w+) mode and returns a file handle .The file is automatically removed when closed (for example, by calling fclose(), or when there are no remaining references to the file handle returned by tmpfile()), or when the script ends.
private function open()
{
if ( !($this->stream = tmpfile()) ) {
fwrite($this->stream, $videodata);
rewind($this->stream);
die('Could not open stream for reading');
}
// now your temp file is ready to be read.
}
So now you have no responsibility of temp. deletion PHP will handle that for you.If you need to customize the temp. file name or get details you may use this http://php.net/manual/en/function.sys-get-temp-dir.php
If you don't want to use files at all and rather want to use purely in memory solution then you can try using memory streams
private function open()
{
if ( !($this->stream = fopen('php://memory', 'wb+') ) ) {
fwrite($this->stream, $videodata);
rewind($this->stream);
die('Could not open stream for reading');
}
// now your in-memory file is ready to be read.
}
You can fread, fwrite, file_get_contents on the memory stream or throw it over the network using the tcp streams. But I must say that the second solution is a bit memory intensive so not suitable for streaming huge files.
Writing a temporary file seems like a temporary workaround for me. As you are getting a stream from the database and you need to serve a stream to the client, there is no need to store data on the harddisk at all.
All you need to do is to replace the part of your linked example that reads from file (and echo's the binary content to the client) and make it read from sql instead.
In your linked example function stream(), replace this part
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;
}
Replace by somthing like this:
/* Execute the query. */
$stmt = sqlsrv_query($conn, $tsql, $params);
if( $stmt === false )
{
echo "Error in statement execution.</br>";
die( print_r( sqlsrv_errors(), true));
}
/* Retrieve and display the data.
The return data is retrieved as a binary stream. */
if ( sqlsrv_fetch( $stmt ) )
{
$videostream = sqlsrv_get_field( $stmt, 0,
SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
//header("Content-Type: image/jpg");
fpassthru($videostream );
}
else
{
echo "Error in retrieving data.</br>";
die(print_r( sqlsrv_errors(), true));
}
For posterity, here's my final result, which seems to work well without needing temp files.
Not certain if it would make more sense to include the query into the class and just pass the $MediaFileID to the class, but this works as is, so for now I've left it.
The entire modified class is included with its original credit info:
function ExecutereadMediaSP($MediaFileID){
try{
$connection = ConnectToDB();
ini_set('memory_limit', '-1');
// logs basic info about media viewer. Mainly for a basic hit counter.
LogMediaRequest($connection, $MediaFileID);
//Selects binary data from SQL Server based on MediaFileID. Passes this to Stream Class. May be able to make further improvements later.
$sql = "SELECT ... data from FILESTREAM column based on file ID ...";
$rst = $connection->prepare($sql);
$rst->execute();
$rst->bindColumn(1, $filecontent, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $rst->fetch(PDO::FETCH_ASSOC);//sql can only return one row due to unique identifier
//Stream file
$stream = new VideoStream($filecontent);
$stream->start();
//Clean up.
$rst->closeCursor();
unset($rst);
$connection = null;
} catch (Exception $e) {
error_log("Error in getting video\n".$e->getMessage(),0);
}
}
/**
* VideoStream - PHP class that supports (adaptive) streaming of files
*
* #author Rana
* modified by HazCod to use stream_get_contents and correct session shutoff
* https://github.com/HazCod
* #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($filecontent)
{
try{
// Opens file handle resource to replace use of actual file in the file system.
$file_handle = fopen('php://memory', 'r+', false, stream_context_create());
// Writes data from SQL Query to file wrapper.
fwrite($file_handle, $filecontent);
// Moves pointer to beginning of file.
fseek($file_handle, 0);
// Gets info on the "file." Required to get filesize.
$fstat = array();
// gather statistics
$fstat = fstat($file_handle);
//Set File Size for Stream Class.
$this->size = $fstat['size'];
// Define Stream as "File."
$this->stream = $file_handle;
} catch (PDOException $e) {
error_log("Error in getting video\n".$e->getMessage(),0);
}
}
/**
* 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->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 && connection_aborted() == 0) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = stream_get_contents($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
session_write_close(); //ensure our session is written away before streaming, else we cannot use it elsewhere
$this->setHeader();
$this->stream();
$this->end();
}
}

How to open video/mp4 with php

I'm trying to read mp4 files with PHP, my initial code was
$file = 'https://s3-sa-east-1.amazonaws.com/onlytestes/video.mp4';
header('Content-type: video/mp4');
readfile($file);
But that way I couldn't use the length bar of the video, skip or even go back, until the video is 100% loaded.
Of course when I read the file directly (video.mp4) everything is fine.
I solved this problem with the following code:
$request = 'video.mp4';
$file = $request;
$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");
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();
But it only works with local files, I think HTTP_RANGE does not work, it returns the following error in the console
''Failed to load resource: the server responded with a status of 416 (Requested Range Not Satisfiable)''
I need to read Amazon S3 videos, for example: video here
Does anyone have any ideas?
Have you read Document Manual Amazon SDK..?
(1) Download latest stable version of SDK
(2) Extract the .zip file & place in wamp/www folder
(3) Rename config-sample.inc.php file to config.inc.php
(4) Add the access key & secret key (retrieved from Amazon S3 account) into
above file, save & exit
(5) create a sample file to display public / private objects from Amazon S3
thank you Yusnur Hidayah
But I still have problems,
Opening the file by the SDK / StreamWrapper, it takes about 18 seconds for the video to start, see
http://192.241.159.176/file.php
This way it is impossible to make available to users
Usually opens in 1 or 2 seconds, see
https://s3-sa-east-1.amazonaws.com/onlytestes/video.mp4

Playing Video - Server is not correctly configured - 12939

I have an ios app with laravel (lumen) on the server side. I am trying to play the videos in the server, on application.
I am using a Player that plays videos with a direct link (e.g vine video link), however when I save the same vine video on my local server, the application doesn't play the video. In fact, when I try the video with my api route, surprisingly it plays the video on Chrome! But on the application end, I receive error:
The server is not correctly configured - 12939
(Please note that if I copy the same mp4 file into the xCode project, add it on 'copy bundle resources', and try with fileWithPath, it works. So I believe it's definitely caused by the server, not vidoo file/codec. )
My route: $app->get('/player/{filename}', 'PlayerController#show');
Methods:
public function show ($filename)
{
$this->playVideo($filename, 'recordings');
}
public function playVideo($filename, $showType)
{
if (file_exists("../uploads/" . $showType . "/" . $filename)) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, "../uploads/" . $showType . "/" . $filename);
header("Content-Type: " . $type);
readfile("../uploads/" . $showType . "/" . $filename);
}
}
To recap my problem, the video is playing on Chrome but receiving '12939' -
'Server is not correctly configured' on the mobile app.
Edit:
I tried using this as mentioned in the Apple Documentations:
curl --range 0-99 http://myapi.dev/test.mp4 -o /dev/null
However the documentation says:
"If the tool reports that it downloaded 100 bytes, the media server correctly handled the byte-range request. If it downloads the entire file, you may need to update the media server."
I received 100% and it downloaded the whole file for me, so I believe this is my problem. But I am not sure how to overcome this issue? What am I doing wrong? What should I do?
To wrap up, this solved my problem:
Placed this to VideoController:
public function streamVideo()
{
$video_path = 'somedirectory/somefile.mp4';
$stream = new VideoStream($video_path);
$stream->start();
}
and then created a file in app > helpers > VideoStream.php:
<?php
{
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();
}
}
Source: http://laravel.io/forum/10-06-2014-streaming-video-files-with-laravel
I ran into a problem similar to this one, but your case is going to take a bit more configuration.
The headers for iOS need to be set appropriately using range requests, and the only way I was able to do that for all browsers was by reworking this gist to what I needed.
In my case, I did something like this in Laravel:
//Video controller
...
public function showVideo($id){
$video = Video::find($id);
return $this->videoService->stream($video);
}
//Video Service
namespace App\Services\VideoService;
use Illuminate\Routing\ResponseFactory as Response;
use App\Models\Video;
class VideoService implements VideoServiceInterface
{
protected $response;
protected $video;
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
public function __construct(Response $response){
$this->response = $response;
}
public function stream(Video $video){
$this->video = $video;
return $this->response->stream(function(){
$this->start();
});
}
//Implement the rest of the gist here, renaming where appropriate....
}
Problem is, you're using Lumen which does not support a stream method on the response factory. What you'll need to do is rework the code I gave you to include Symfony's StreamedResponse Object.
If you look at how Laravel does it, you can probably do something like this:
//Video Service
namespace App\Services\VideoService;
use Symfony\Component\HttpFoundation\StreamedResponse as Response;
use App\Models\Video;
class VideoService implements VideoServiceInterface
{
protected $video;
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
public function stream(Video $video){
$this->video = $video;
return new Response(function(){
$this->start();
});
}
//Implement the rest of the gist here, renaming where appropriate....
}
This isn't exact, and it won't work for you out of the box. But this should give you all of the components you need to register your own Service Provider and apply this appropriately to your own use-case.
Good luck.

Stream video through php server [duplicate]

I have a 200MB file that I want to give to a user via download. However, since we want the user to only download this file once, we are doing this:
echo file_get_contents('http://some.secret.location.com/secretfolder/the_file.tar.gz');
to force a download. However, this means that the whole file has to be loaded in memory, which usually doesn't work. How can we stream this file to them, at some kb per chunk?
Try something like this (source http://teddy.fr/2007/11/28/how-serve-big-files-through-php/):
<?php
define('CHUNK_SIZE', 1024*1024); // Size (in bytes) of tiles chunk
// Read a file and display its content chunk by chunk
function readfile_chunked($filename, $retbytes = TRUE) {
$buffer = '';
$cnt = 0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, CHUNK_SIZE);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
// Here goes your code for checking that the user is logged in
// ...
// ...
if ($logged_in) {
$filename = 'path/to/your/file';
$mimetype = 'mime/type';
header('Content-Type: '.$mimetype );
readfile_chunked($filename);
} else {
echo 'Tabatha says you haven\'t paid.';
}
?>
Use fpassthru(). As the name suggests, it doesn't read the entire file into memory prior to sending it, rather it outputs it straight to the client.
Modified from the example in the manual:
<?php
// the file you want to send
$path = "path/to/file";
// the file name of the download, change this if needed
$public_name = basename($path);
// get the file's mime type to send the correct content type header
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $path);
// send the headers
header("Content-Disposition: attachment; filename=$public_name;");
header("Content-Type: $mime_type");
header('Content-Length: ' . filesize($path));
// stream the file
$fp = fopen($path, 'rb');
fpassthru($fp);
exit;
If you would rather stream the content directly to the browser rather than a download (and if the content type is supported by the browser, such as video, audio, pdf etc) then remove the Content-Disposition header.
Take a look at the example from the manual page of fsockopen():
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
This will connect to www.example.com, send a request then get and echo the response in 128 byte chunks. You may want to make it more than 128 bytes.
I found this method in http://codesamplez.com/programming/php-html5-video-streaming-tutorial
And it works very well for me
<?php
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();
}
}
To use this class, you will have to write simple code like as below:
$stream = new VideoStream($filePath);
$stream->start();
I ran into this problem as well using readfile() to force a download. The memory problem lies not within readfile, rather with ouput buffering.
Just make sure you switch off output buffering before readfile, and the problem should be fixed.
if (ob_get_level()) ob_end_clean();
readfile($yourfile);
Works for files with a size much larger than the allocated memory limit.

Categories