Building a video streaming platform doesn’t require expensive third-party services or complex infrastructure. With PHP, you can create your own robust video streaming solution that handles everything from basic playback to advanced seeking functionality. I worked with PHP video streaming in one of my work project, and I’m excited to share the exact techniques that transformed my simple video server into a professional-grade streaming platform.
Video content dominates the internet. Users expect seamless playback, instant seeking, and fast loading times. Traditional video serving methods simply dump the entire file to the browser, which creates terrible user experiences and wastes bandwidth.
PHP video streaming solves these problems by implementing byte range requests and progressive loading. This means your users get instant playback, perfect seeking functionality, and your server saves massive amounts of bandwidth.
Before diving into the code, you need to understand how modern video streaming actually works. When a user clicks play or seeks to a specific timestamp, their browser sends a Range header to your server. This header tells your PHP script exactly which bytes of the video file to send back.
For example:
Range: bytes=0-1023 requests the first 1024 bytesRange: bytes=1024-2047 requests the next chunkRange: bytes=500000- requests everything from byte 500,000 onwardsThis is revolutionary because it enables true streaming functionality. Users can jump to any part of your video instantly without downloading the entire file first.
Here’s the updated and improved VideoStream class that handles all modern streaming requirements:
<?php
class VideoStream
{
private $path = "";
private $stream = null;
private $buffer = 262144; // 256KB buffer for optimal performance
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath)
{
$this->path = $filePath;
}
/**
* Initialize and validate the video file
*/
private function init()
{
// Validate file exists and is readable
if (!file_exists($this->path) || !is_readable($this->path)) {
header("HTTP/1.1 404 Not Found");
exit;
}
$this->size = sprintf("%u", filesize($this->path));
$this->start = 0;
$this->end = $this->size - 1;
}
/**
* Parse Range header and set start/end positions
*/
private function parseRange()
{
if (!isset($_SERVER['HTTP_RANGE']) ||
!preg_match('/bytes=(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches)) {
return;
}
$start = $matches[1];
$end = $matches[2];
if (!empty($start)) {
$this->start = intval($start);
}
if (!empty($end)) {
$this->end = min(intval($end), $this->size - 1);
}
}
/**
* Set appropriate headers for video streaming
*/
private function setHeaders()
{
// Prevent caching issues
header("Cache-Control: no-cache, no-store, must-revalidate");
header("Pragma: no-cache");
header("Expires: 0");
// Set content type based on file extension
$mimeType = $this->getMimeType();
header("Content-Type: {$mimeType}");
// Set range headers
header("Accept-Ranges: bytes");
header("Content-Length: " . ($this->end - $this->start + 1));
if ($this->start > 0 || $this->end < ($this->size - 1)) {
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes {$this->start}-{$this->end}/{$this->size}");
}
}
/**
* Get MIME type for video file
*/
private function getMimeType()
{
$extension = strtolower(pathinfo($this->path, PATHINFO_EXTENSION));
$mimeTypes = [
'mp4' => 'video/mp4',
'webm' => 'video/webm',
'ogg' => 'video/ogg',
'avi' => 'video/x-msvideo',
'mov' => 'video/quicktime',
'wmv' => 'video/x-ms-wmv',
'flv' => 'video/x-flv'
];
return isset($mimeTypes[$extension]) ? $mimeTypes[$extension] : 'application/octet-stream';
}
/**
* Stream the video content to browser
*/
public function start()
{
$this->init();
$this->parseRange();
$this->setHeaders();
// Create stream context for better performance
$context = stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => 30
]
]);
if (!($this->stream = fopen($this->path, 'rb', false, $context))) {
header("HTTP/1.1 500 Internal Server Error");
exit;
}
// Seek to start position
if ($this->start > 0) {
fseek($this->stream, $this->start);
}
// Stream the content in chunks
$this->readBuffer();
fclose($this->stream);
}
/**
* Read and output file buffer
*/
private function readBuffer()
{
$bytesToRead = $this->end - $this->start + 1;
while (!feof($this->stream) && $bytesToRead > 0) {
$chunkSize = min($this->buffer, $bytesToRead);
$chunk = fread($this->stream, $chunkSize);
if ($chunk === false) {
break;
}
echo $chunk;
flush();
$bytesToRead -= strlen($chunk);
// Prevent timeout on slow connections
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
}
}
?>
Code language: HTML, XML (xml) Creating your streaming endpoint is surprisingly straightforward. Here’s how you implement it:
<?php
// stream.php - Your video streaming endpoint
require_once 'VideoStream.php';
// Validate video parameter
$videoFile = isset($_GET['video']) ? $_GET['video'] : null;
if (empty($videoFile)) {
header("HTTP/1.1 400 Bad Request");
echo "Video parameter is required";
exit;
}
// Sanitize file path to prevent directory traversal attacks
$videoFile = basename($videoFile);
$videoPath = "videos/" . $videoFile;
// Additional security: check file extension
$allowedExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi'];
$fileExtension = strtolower(pathinfo($videoFile, PATHINFO_EXTENSION));
if (!in_array($fileExtension, $allowedExtensions)) {
header("HTTP/1.1 403 Forbidden");
echo "File type not allowed";
exit;
}
// Initialize and start streaming
$stream = new VideoStream($videoPath);
$stream->start();
?>
Code language: HTML, XML (xml) The magic happens when you combine your PHP streaming backend with HTML5 video elements. Here’s the complete frontend implementation:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Video Streaming Player</title>
<style>
.video-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
video {
width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.video-controls {
margin-top: 15px;
text-align: center;
}
.video-list {
margin-bottom: 20px;
}
.video-btn {
margin: 5px;
padding: 10px 15px;
background: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.video-btn:hover {
background: #005a8c;
}
</style>
</head>
<body>
<div class="video-container">
<h1>Professional Video Streaming Platform</h1>
<div class="video-list">
<button class="video-btn" onclick="loadVideo('sample-video.mp4')">Sample Video 1</button>
<button class="video-btn" onclick="loadVideo('demo-content.webm')">Demo Content</button>
<button class="video-btn" onclick="loadVideo('tutorial.mov')">Tutorial Video</button>
</div>
<video id="videoPlayer" controls preload="metadata">
<source src="" type="">
Your browser doesn't support HTML5 video streaming.
</video>
<div class="video-controls">
<p>Click any video above to start streaming instantly!</p>
</div>
</div>
<script>
function loadVideo(filename) {
const video = document.getElementById('videoPlayer');
const source = video.getElementsByTagName('source')[0];
// Update source with streaming endpoint
source.src = `stream.php?video=${encodeURIComponent(filename)}`;
source.type = getVideoMimeType(filename);
// Reload video element
video.load();
// Auto-play after loading
video.addEventListener('loadeddata', function() {
video.play().catch(e => {
console.log('Autoplay prevented by browser policy');
});
}, { once: true });
}
function getVideoMimeType(filename) {
const extension = filename.split('.').pop().toLowerCase();
const mimeTypes = {
'mp4': 'video/mp4',
'webm': 'video/webm',
'ogg': 'video/ogg',
'mov': 'video/quicktime',
'avi': 'video/x-msvideo'
};
return mimeTypes[extension] || 'video/mp4';
}
// Enhanced video player with streaming analytics
document.getElementById('videoPlayer').addEventListener('progress', function() {
const buffered = this.buffered;
if (buffered.length > 0) {
const bufferedEnd = buffered.end(buffered.length - 1);
const duration = this.duration;
const bufferedPercent = (bufferedEnd / duration) * 100;
console.log(`Buffered: ${bufferedPercent.toFixed(1)}%`);
}
});
</script>
</body>
</html>
Code language: HTML, XML (xml) Security becomes critical when you’re serving video content. Here’s how to protect your streaming server:
<?php
// Add authentication to your streaming endpoint
session_start();
function validateUser() {
if (!isset($_SESSION['user_id']) || empty($_SESSION['user_id'])) {
header("HTTP/1.1 401 Unauthorized");
exit("Authentication required");
}
}
function checkVideoAccess($userId, $videoFile) {
// Implement your access control logic here
// Check database permissions, subscription status, etc.
return true; // Return false to deny access
}
?>
Code language: HTML, XML (xml) <?php
class RateLimiter {
private $maxRequests = 50; // Requests per hour
public function checkLimit($identifier) {
$key = "rate_limit_" . md5($identifier);
$requests = apcu_fetch($key, $success);
if (!$success) {
$requests = 0;
}
if ($requests >= $this->maxRequests) {
header("HTTP/1.1 429 Too Many Requests");
exit("Rate limit exceeded");
}
apcu_store($key, $requests + 1, 3600);
}
}
?>
Code language: HTML, XML (xml) For production environments, you’ll want to integrate with Content Delivery Networks:
<?php
function getCDNUrl($videoFile) {
$cdnDomain = "https://your-cdn.example.com";
$hashedPath = md5($videoFile . time());
return "{$cdnDomain}/stream/{$hashedPath}/{$videoFile}";
}
?>
Code language: HTML, XML (xml) The difference between a good streaming server and a great one lies in optimization. Here’s what actually matters:
Different video types require different buffer sizes. Large buffers work better for high-quality videos but consume more memory. Start with 256KB and adjust based on your specific needs.
Always use fread() with proper chunk sizes instead of loading entire files into memory. This prevents memory exhaustion on large video files.
Implement proper connection status checking to prevent wasted resources on closed connections.
Safari on macOS can be particularly challenging with video streaming. The solution involves setting specific headers:
// Add these headers for Safari compatibility
header("Accept-Ranges: bytes");
header("Content-Transfer-Encoding: binary");
Code language: JavaScript (javascript) If users experience slow seeking, check your buffer size and ensure you’re properly implementing byte range requests. The parseRange() method in our class handles this automatically.
Mobile browsers require additional considerations:
// Mobile-specific optimizations
if (preg_match('/Mobile|Android|iPhone|iPad/', $_SERVER['HTTP_USER_AGENT'])) {
$this->buffer = 65536; // Smaller buffer for mobile
}
Code language: PHP (php) Before launching your PHP video streaming platform, verify these critical elements:
PHP video streaming opens incredible possibilities for developers who want full control over their video delivery. The streaming class we’ve built handles byte range requests perfectly, provides excellent seeking performance, and scales beautifully with proper optimization.
Remember that successful video streaming depends on both server-side implementation and client-side experience. The combination of our PHP streaming backend with modern HTML5 video elements creates a professional-grade platform that rivals expensive third-party solutions.
Start implementing this streaming solution today, and you’ll quickly discover why so many developers choose PHP for video streaming projects. The flexibility, performance, and cost savings make it an absolutely compelling choice for any video-centric application.
Whether you’re building a learning management system, entertainment platform, or corporate video portal, this PHP video streaming foundation will serve you incredibly well. The code is production-ready, secure, and optimized for real-world usage.
Want to level up your PHP skill? Explore more PHP Tutorials!
Learn python file handling from scratch! This comprehensive guide walks you through reading, writing, and managing files in Python with real-world examples, troubleshooting tips, and…
You've conquered the service worker lifecycle, mastered caching strategies, and explored advanced features. Now it's time to lock down your implementation with battle-tested service worker…
Unlock the full potential of service workers with advanced features like push notifications, background sync, and performance optimization techniques that transform your web app into…
This website uses cookies.
View Comments
Hi,
I am trying to play .mp3 files from amazon s3.. Unable to play 16 MB files from amaozon s3 using above code getting "GET http://192.168.1.253/php/test/index.php net::ERR_CONTENT_LENGTH_MISMATCH " error ..Please suggest solution to resolve this..
Hello,
Thanks for trying this video streaming tutorial! However, the link you gave isn't working for me. Please use other sharing platform(dropbox/gist etc)
Rana ,
Is it possible to show a part of the video
For exampe , a part from timstamp 1:15 to 2:15
thank you
Alain
thank you :D
It takes lots of time to load the video for the 1st time. I am playing 50 mb video file using the same code given above. Any thoughts?
Did you compare the load time with direct media file loading and via PHP streaming(if so, give some link to test)? Also, it may depends whether you are streaming from S3, as such cases can cause slow loading as well(depending on bandwidth of your server and s3's connectivity)
This tutorial is great !!..
I have a question. Does the this streaming takes up bandwidth of the local server or s3 itself ?
I afraid, this will take both S3 and local server bandwidth, unless you cache the s3 on local machine. This is what I did in my case. First attempt to load video will be streamed directly from s3 and keep a backup on local machine. For consecutive requests, serve from local version.
I thought so. Thanks for the reply.
Hi! Thanks a lot for the tutorial.
Do you know how to make it loop?
Thank you for the article. How do you feed the stream to the player? Do you echo $stream->start(); to the src attribute of a HTML5 tag? thank you!
@vasilis, no. You have to give the php script's url in the src attribute of a HTML5 tag.
Hi
Great article, I am also trying to stream to a player but the video is taking over my whole page how do i make the video is just added to my html5 player. Thanks
Put the streaming code ($stream = new VideoStream($filePath);
$stream->start();) in a separate php file eg stream.php , and then in a different php file put video tag and in the src attribute put stream.php ,
<video width="100%" height="500"
it will stream without taking the whole page.
Its not working
Hello, I also tried to feed my HTML5 player with it, but it's not working, and I don't understand what you mean by "give the script url", what should I input ? Thank you a lot !
Thanks a lot for the quick reply. I implemented the video streaming for two videos, one local in the server and one in S3. The local video plays well with all the browsers. The video I stream from S3 plays fine in Firefox and Safari but in Chrome I receive error net::ERR_CONTENT_LENGTH_MISMATCH in the middle of the video (similar to the one reported in the 1st message). However, Firefox seems not to use the Range HTTP header, unlike Chrome.
You can test the videos using the following links, the first streams the local video, the second streams the video from S3.
http://23.20.72.172/player/player.php
http://23.20.72.172/player/s3player.php
In the apache error log I get the following error:
"PHP Fatal error: Uncaught exception 'Guzzle\\Common\\Exception\\RuntimeException' with message 'Cannot seek to byte 5382346 when the buffered stream contains only 0 bytes" for this line:
$context = stream_context_create(array(
's3' => array(
'seekable' => true
)
));
Do you think it's a bug in your code, chrome or S3?
Thanks a lot,
Vasilis
Hello vasilis, I have checked your s3 player on my chrome and its working ok(on mac osx). So, I guess, its a problem from chrome browser from your side. So, I can suggest you to try checking this thread on this net::ERR_CONTENT_LENGTH_MISMATCH error if it can help solve your issue. Also, Feel free to share your experience, if you become successful to solve it, on this post by commenting so that other readers get help if they suffer from similar issue.
Warning: fopen() [function.fopen]: Filename cannot be empty in G:\g\xampp\htdocs\strm.php on line 27
Could not open stream for reading....hi am new to php...and how to call $stream->start(); in video tag ...and how we set the local video path..... thanks in advance
Hello krishna, in video tag, you won't be calling the '$stream->start()' method. Rather you will give the url of the php file that calls that method. I already gave an example above. Hope these helps!
I to have the same doubt as krishna said.... plz will to explain with more clarity thanks!
Hi Ahsan, I was able to solve my problem by making the $buffer very small, only 512. Now it works fine although I'm not sure why, it looks specific to S3 stream wrapper. I'll test it with more videos.
i'am trying to build a webapp to stream videos from client to rtmp server without uploading the file. is it possible ??