1 | <?php |
||||
2 | |||||
3 | namespace Iman\Streamer; |
||||
4 | |||||
5 | use Iman\Streamer\Events\VideoStreamEnded; |
||||
6 | use Iman\Streamer\Events\VideoStreamStarted; |
||||
7 | |||||
8 | class VideoStreamer |
||||
9 | { |
||||
10 | private $path = ''; |
||||
11 | |||||
12 | private $stream = ''; |
||||
13 | |||||
14 | private $buffer = 102400; |
||||
15 | |||||
16 | private $start = -1; |
||||
17 | |||||
18 | private $end = -1; |
||||
19 | |||||
20 | private $size = 0; |
||||
21 | |||||
22 | private $mime = ''; |
||||
23 | |||||
24 | /** |
||||
25 | * @var Video |
||||
26 | */ |
||||
27 | private $video; |
||||
28 | |||||
29 | public function __construct($filePath, $stream) |
||||
30 | { |
||||
31 | $this->path = $filePath; |
||||
32 | $this->stream = $stream; |
||||
33 | $this->mime = mime_content_type($filePath); |
||||
34 | |||||
35 | $this->video = new Video(); |
||||
36 | $this->video->setPath($this->path); |
||||
37 | } |
||||
38 | |||||
39 | /** |
||||
40 | * Set proper header to serve the video content. |
||||
41 | */ |
||||
42 | private function setHeader() |
||||
43 | { |
||||
44 | ob_get_clean(); |
||||
45 | header('Content-Type: '.$this->mime); |
||||
46 | header('Cache-Control: max-age=2592000, public'); |
||||
47 | header('Expires: '.gmdate('D, d M Y H:i:s', time() + 2592000).' GMT'); |
||||
48 | header('Last-Modified: '.gmdate('D, d M Y H:i:s', @filemtime($this->path)).' GMT'); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
49 | $this->start = 0; |
||||
50 | $this->size = filesize($this->path); |
||||
51 | $this->end = $this->size - 1; |
||||
52 | header('Accept-Ranges: 0-'.$this->end); |
||||
53 | |||||
54 | if (! isset($_SERVER['HTTP_RANGE'])) { |
||||
55 | header('Content-Length: '.$this->size); |
||||
56 | |||||
57 | return; |
||||
58 | } |
||||
59 | |||||
60 | $c_end = $this->end; |
||||
61 | [ |
||||
62 | , |
||||
63 | $range, |
||||
64 | ] = explode('=', $_SERVER['HTTP_RANGE'], 2); |
||||
65 | |||||
66 | if (strpos($range, ',') !== false) { |
||||
67 | header('HTTP/1.1 416 Requested Range Not Satisfiable'); |
||||
68 | header("Content-Range: bytes $this->start-$this->end/$this->size"); |
||||
69 | exit; |
||||
70 | } |
||||
71 | |||||
72 | if ($range == '-') { |
||||
73 | $c_start = $this->size - substr($range, 1); |
||||
74 | } else { |
||||
75 | $range = explode('-', $range); |
||||
76 | $c_start = $range[0]; |
||||
77 | |||||
78 | $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end; |
||||
79 | } |
||||
80 | $c_end = ($c_end > $this->end) ? $this->end : $c_end; |
||||
81 | if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) { |
||||
82 | header('HTTP/1.1 416 Requested Range Not Satisfiable'); |
||||
83 | header("Content-Range: bytes $this->start-$this->end/$this->size"); |
||||
84 | exit; |
||||
85 | } |
||||
86 | $this->start = $c_start; |
||||
87 | $this->end = $c_end; |
||||
88 | $length = $this->end - $this->start + 1; |
||||
89 | fseek($this->stream, $this->start); |
||||
0 ignored issues
–
show
It seems like
$this->start can also be of type string ; however, parameter $offset of fseek() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
90 | header('HTTP/1.1 206 Partial Content'); |
||||
91 | header('Content-Length: '.$length); |
||||
92 | header("Content-Range: bytes $this->start-$this->end/".$this->size); |
||||
93 | } |
||||
94 | |||||
95 | /** |
||||
96 | * close curretly opened stream. |
||||
97 | */ |
||||
98 | private function end() |
||||
99 | { |
||||
100 | fclose($this->stream); |
||||
101 | event(new VideoStreamEnded($this->video)); |
||||
102 | exit; |
||||
0 ignored issues
–
show
|
|||||
103 | } |
||||
104 | |||||
105 | /** |
||||
106 | * perform the streaming of calculated range. |
||||
107 | */ |
||||
108 | private function stream() |
||||
109 | { |
||||
110 | $this->video->setProgress($this->start); |
||||
111 | event(new VideoStreamStarted($this->video)); |
||||
112 | |||||
113 | $i = $this->start; |
||||
114 | set_time_limit(0); |
||||
115 | while (! feof($this->stream) && $i <= $this->end) { |
||||
116 | $this->video->setProgress($i); |
||||
117 | |||||
118 | $bytesToRead = $this->buffer; |
||||
119 | if (($i + $bytesToRead) > $this->end) { |
||||
120 | $bytesToRead = $this->end - $i + 1; |
||||
121 | } |
||||
122 | $data = fread($this->stream, $bytesToRead); |
||||
123 | echo $data; |
||||
124 | flush(); |
||||
125 | $i += $bytesToRead; |
||||
126 | } |
||||
127 | } |
||||
128 | |||||
129 | /** |
||||
130 | * Start streaming video content. |
||||
131 | */ |
||||
132 | public function start() |
||||
133 | { |
||||
134 | $this->setHeader(); |
||||
135 | $this->stream(); |
||||
136 | $this->end(); |
||||
137 | } |
||||
138 | |||||
139 | public static function streamFile($path) |
||||
140 | { |
||||
141 | $stream = fopen($path, 'rb'); |
||||
142 | if (! $stream) { |
||||
0 ignored issues
–
show
|
|||||
143 | throw new \Exception('File not found in: '.$path, 6542); |
||||
144 | } |
||||
145 | |||||
146 | (new static($path, $stream))->start(); |
||||
147 | } |
||||
148 | } |
||||
149 |