PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.1
Presto Player v4.3.1
4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / inc / Services / Streamer.php
presto-player / inc / Services Last commit date
API 1 month ago Blocks 10 months ago License 1 month ago AdminNotice.php 3 weeks ago AdminNotices.php 3 weeks ago AjaxActions.php 3 weeks ago Blocks.php 1 year ago Compatibility.php 10 months ago Learn.php 1 month ago Menu.php 1 month ago Migrations.php 1 year ago NpsSurvey.php 5 months ago Onboarding.php 3 weeks ago Player.php 3 days ago PluginInstaller.php 1 month ago PreloadService.php 1 year ago ProCompatibility.php 1 month ago ReusableVideos.php 2 weeks ago RewriteRulesManager.php 1 year ago Scripts.php 2 weeks ago Settings.php 1 month ago Shortcodes.php 1 month ago Streamer.php 3 weeks ago Translation.php 3 weeks ago Usage.php 3 weeks ago VideoPostType.php 2 weeks ago
Streamer.php
188 lines
1 <?php
2 /**
3 * Byte-range video streamer.
4 *
5 * @package PrestoPlayer
6 */
7
8 namespace PrestoPlayer\Services;
9
10 /**
11 * Streams a file to the browser with HTTP byte-range support.
12 */
13 class Streamer {
14
15 /**
16 * Path to the file being streamed.
17 *
18 * @var string
19 */
20 private $path = '';
21
22 /**
23 * Open file stream resource.
24 *
25 * @var resource|false|string
26 */
27 private $stream = '';
28
29 /**
30 * Number of bytes to read per chunk.
31 *
32 * @var int
33 */
34 private $buffer = 0;
35
36 /**
37 * Start byte of the requested range.
38 *
39 * @var int
40 */
41 private $start = -1;
42
43 /**
44 * End byte of the requested range.
45 *
46 * @var int
47 */
48 private $end = -1;
49
50 /**
51 * Total size of the file in bytes.
52 *
53 * @var int
54 */
55 private $size = 0;
56
57 /**
58 * MIME type of the file being streamed.
59 *
60 * @var string
61 */
62 private $type = '';
63
64 /**
65 * Constructor.
66 *
67 * @param string $file_path Path to the file to stream.
68 * @param string $file_type MIME type of the file.
69 * @param int $buffer Number of bytes to read per chunk.
70 */
71 public function __construct( $file_path, $file_type, $buffer = 102400 ) {
72 $this->path = $file_path;
73 $this->type = $file_type;
74 $this->buffer = $buffer;
75 }
76
77 /**
78 * Start streaming video content.
79 *
80 * @return void
81 */
82 public function start() {
83 $this->open();
84 $this->set_header();
85 $this->stream();
86 $this->end();
87 }
88
89 /**
90 * Open stream
91 */
92 private function open() {
93
94 $this->stream = fopen( $this->path, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Byte-range video streaming requires a low-level file handle; WP_Filesystem cannot stream ranges.
95
96 if ( ! $this->stream ) {
97 die( 'Could not open stream for reading' );
98 }
99 }
100
101 /**
102 * Set proper header to serve the video content
103 */
104 private function set_header() {
105
106 $this->start = 0;
107 $this->size = filesize( $this->path );
108 $this->end = $this->size - 1;
109
110 ob_get_clean();
111 header( 'Content-Type: ' . $this->type );
112 header( 'Cache-Control: max-age=2592000, public' );
113 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 2592000 ) . ' GMT' );
114 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', @filemtime( $this->path ) ) . ' GMT' );
115 header( 'Accept-Ranges: 0-' . $this->end );
116
117 if ( isset( $_SERVER['HTTP_RANGE'] ) ) {
118
119 $c_start = $this->start;
120 $c_end = $this->end;
121
122 $parts = explode( '=', sanitize_text_field( wp_unslash( $_SERVER['HTTP_RANGE'] ) ), 2 );
123 if ( count( $parts ) < 2 ) {
124 header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
125 header( "Content-Range: bytes $this->start-$this->end/$this->size" );
126 exit;
127 }
128 $range = $parts[1];
129 if ( strpos( $range, ',' ) !== false ) {
130 header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
131 header( "Content-Range: bytes $this->start-$this->end/$this->size" );
132 exit;
133 }
134 if ( '-' == $range ) {
135 $c_start = $this->size - substr( $range, 1 );
136 } else {
137 $range = explode( '-', $range );
138 $c_start = $range[0];
139 $c_end = ( isset( $range[1] ) && is_numeric( $range[1] ) ) ? $range[1] : $c_end;
140 }
141 $c_end = ( $c_end > $this->end ) ? $this->end : $c_end;
142 if ( $c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size ) {
143 header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
144 header( "Content-Range: bytes $this->start-$this->end/$this->size" );
145 exit;
146 }
147 $this->start = $c_start;
148 $this->end = $c_end;
149 $length = $this->end - $this->start + 1;
150 fseek( $this->stream, $this->start );
151 header( 'HTTP/1.1 206 Partial Content' );
152 header( 'Content-Length: ' . $length );
153 header( "Content-Range: bytes $this->start-$this->end/" . $this->size );
154 } else {
155
156 header( 'Content-Length: ' . $this->size );
157 }
158 }
159
160 /**
161 * Perform the streaming of calculated range.
162 */
163 private function stream() {
164
165 $i = $this->start;
166 set_time_limit( 0 );
167
168 while ( ! feof( $this->stream ) && $i <= $this->end ) {
169 $bytes_to_read = $this->buffer;
170 if ( ( $i + $bytes_to_read ) > $this->end ) {
171 $bytes_to_read = $this->end - $i + 1;
172 }
173 $data = fread( $this->stream, $bytes_to_read ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Byte-range video streaming requires direct stream reads.
174 echo $data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw video byte stream; escaping would corrupt binary output.
175 flush();
176 $i += $bytes_to_read;
177 }
178 }
179
180 /**
181 * Close currently opened stream.
182 */
183 private function end() {
184 fclose( $this->stream ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the stream handle opened for byte-range streaming.
185 exit;
186 }
187 }
188