tikiatua/internal-assets-plugin

MP4 file not playing in Safari

kevadamson opened this issue · 12 comments

Hi. This was working great for me until I checked in Safari, and basically MP4 videos won't play when using this method. Any thoughts? Oh, and I'm using the version that works in Craft 2. Thanks :)

Hi @kevadamson,

This might probably have to do with the mime type that is set for the file. You should check what the http header "Content-Type" is returning for the mp4 file. For videos, the mime type should probably be video/mp4.

The content-type is loaded via "header('Content-type: '.$file->getMimeType());" and uses the corresponding mime-type resolving of the underlying system.

Maybe you need to hard code the content type for mp4 files in the file controllers/InternalAssets_AssetsController.php.

Thanks for getting back so quick :) Do you know the code, and the line number I need to add it?

Look on line 122. You need to add an else-if clause and check if the filename ends with .mp4 and then set the header accordingly.

Thanks :)

Couldn't get this to work with mp4s unfortunately - I've had to disable the plugin for now and use public folder :\

OK. We've actually got Videos working. Here is the additional code (line 129 to 210):

if ($hasAccess) {

    // get the files mime type
    $mimeType = $file->getMimeType();

    // use an optimized header for pdfs
    if ($mimeType == "application/pdf") {
        header('Content-type: application/pdf');
        header('Content-Disposition: inline; filename="' . $filename . '"');
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . filesize($filepath));
        header('Accept-Ranges: bytes');

        readfile($filepath);
    } elseif ($mimeType == "video/mp4") {
        // Determine file mimetype
        $fp = fopen($filepath, "rb");
        $size = filesize($filepath);
        $length = $size;
        $start = 0;
        $end = $size - 1;
        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;

    } else {
        header('Content-type: '.$file->getMimeType());
        readfile($filepath);
    }
} else {
    // throw a 404 exception
    throw new HttpException(404, "File not found or permission denied");
}

BUT we've found an additional problem where users are instantly logged out when viewing a page with the MP4 on when on an iPhone using Safari ...

There might be a hint in a stack overflow discussion here:

I've discovered yesterday the iPhone version of Safari defers the playing of an mp4 file to the quicktime player integrated in the iPhone. This player first fetches the first two bytes of the mp4 (to somehow determine keyframes I suppose). It uses the accept-range header for this. Then the entire file is requested, again using accept-range. I was serving these mp4 files with the use of PHP aswell, and I discovered that supporting this accept-range header everything started working all of a sudden. It might solve your problems with the desktop version of Safari aswell, although that has always worked for me without the accept-range support.
https://stackoverflow.com/questions/3114406/single-php-exit-statement-prevents-html5-video-in-safari/3125869#3125869)

The session is preserved using cookies. The request from the quicktime player might somehow mess with the cookies in the browser.

And here is something from the apple blogs:

We identified that the video player is not sending the session cookies. If you reply with a 403 code, the video player will request again, this time with cookies. However, it is still not working for us.
https://discussions.apple.com/thread/7704776

This one might also be helpful:

HTTP servers hosting media files for iOS must support byte-range requests, which iOS uses to perform random access in media playback. (Byte-range support is also known as content-range or partial-range support.) Most, but not all, HTTP 1.1 servers already support byte-range requests.

If you are not sure whether your media server supports byte-range requests, you can open the Terminal application in OS X and use the curl command-line tool to download a short segment from a file on the server:

curl --range 0-99 http://example.com/test.mov -o /dev/null

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.

Many thanks for your responses on this :) Do you have any thoughts on this: #10?

Proper support for this should be integrated in the Craft 4 version of the plugin. I will put it in the roadmap.

Need to check if this can easily be implemented with the Yii-Response method sendStreamAsFile https://www.yiiframework.com/doc/api/2.0/yii-web-response#sendStreamAsFile()-detail