PHP streaming webm does not work on Safari

158 views Asked by At

The Issue

I'm trying to make .webm streaming on Safari through PHP. For some reason, I'm getting an empty video player in Safari. Works fine on the other browsers.

There are screenshots of all requests that Safari makes and their request and response headers:

  1. play.php
  2. play.php – Byte Range 0-1
  3. play.php – Byte Range 0-all bites

What I have already tried

From the research that I have done so far, I know about the Safari issue when the server does not support the Range header.

I already tested my case by running curl --range 0-99 http://localhost/video/play.php -o /dev/null , and the results show that my server supports custom ranges.

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   100  100   100    0     0   2889      0 --:--:-- --:--:-- --:--:--  3571

Reference: [Failed to Load Resource, Plugin Handled Load on iOS](Failed to Load Resource, Plugin Handled Load on iOS

Testing results

  1. localhost/video/play.php (with webm file) - Works only on Chrome

  2. localhost/video/play.php (with mp4 file) - Works on both Chrome and Safari

  3. localhost/video/test.mp4 - Works on both Chrome and Safari

  4. localhost/video/test.webm - Works on both Chrome and Safari

Directory Structure

video
├── play.php (index file, passes the video file to the VideoStream.php)
├── VideoStream.php (The class for php streaming)
├── test.mp4
└── test.webm

Files

Play.php

<?php

include "VideoStream.php";

// $filePath = './test.mp4';
$filePath = './test.webm';

$stream = new VideoStream($filePath);
$stream->start();

VideoStream.php

<?php

/**
 * Description of VideoStream
 *
 * @author Rana
 * @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
 */

class VideoStream
{
    private $path = "";
    private $extension = "";
    private $stream = "";
    private $buffer = 102400;
    private $start  = -1;
    private $end    = -1;
    private $size   = 0;

    function __construct($filePath)
    {
        $this->path = $filePath;
        $this->extension = pathinfo($filePath)['extension'];
    }

    /**
     * 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();

        $this->start = 0;
        $this->size  = filesize($this->path);
        $this->end   = $this->size - 1;

        header("Content-Type: video/" . $this->extension);
        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');
        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();
    }
}

NGINX

I do use NGINX, not sure is that can couse the issue, but just in case I'm leaving my configuration for the /video/ location:

location /video/ {
    alias /var/www/video/;
    index index.html;

    error_page 404 /usr/share/nginx/html/index.html;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass video:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    try_files $uri $uri/ /index.php$is_args$args /index.php =404;

    autoindex on;
}

Edit: Still looking for a solution.

1

There are 1 answers

1
Test mmeller On

I found out that Safari supports webm files only when URI path ends with .webm (query params doesn't count) - at least on macOS Ventura