【问题标题】:Why can't I cast an MP4 file served by PHP from outside of the document root?为什么我不能从文档根目录之外投射由 PHP 提供的 MP4 文件?
【发布时间】:2019-03-27 02:50:06
【问题描述】:

好吧,我放弃了...发生了一些非常奇怪的事情,在搞砸了几天之后,我不得不寻求帮助。我有一个 PHP 脚本,它从文档根目录之外提供一个 MP4 文件。这个脚本效果很好,除了一个非常重要的(至少对我来说)细节:它不会让我选择投射内容。在同一台服务器上,当我访问文档根目录内的 MP4 文件时,我加载页面,当我单击 Chrome 视频播放器右下角的三个点时,我可以选择下载或投射到我的铬铸。使用我的脚本,我只能选择下载,而且我真的需要投射!我已经对此进行了如此多的调整,以至于任何一种方法的标题输出几乎都是相同的。这是我的代码...

<?php
$file=$_GET['file'];

//validate
if($file=="." || $file==".."){$file="";}

$mediaRoot="../../../hostMedia";
$file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
$file=str_replace('\\',"/",$file);

$filesize = filesize($file);

$offset = 0;
$length = $filesize;

// find the requested range
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);

$offset = intval($matches[1]);

$length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;

// output the right headers for partial content
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
header('Content-Type: video/mp4');
header('Content-Length: ' . $filesize);
header('Accept-Ranges: bytes');
header('Cache-Control: max-age=0');

// open file for reading
$file = fopen($file, 'r');

// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);

// populate $data with all except the last byte of the file
$numBytes=($filesize-1);
$dataLen=0;
while($dataLen<$numBytes){
    $lenGrab=($numBytes-$dataLen);
    if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
    $data=fread($file, $lenGrab);
    print($data);
    $dataLen+=strlen($data);
}

// close file
fclose($file);
?>

感谢解决这个问题的人!

更新

好的,听从@Brian Heward 的建议,我花了无数时间确保标题完全一致!!!我非常确定它会起作用,但可惜,它仍然无法让我选择投射。这是我更新的 PHP...

<?php
session_start();

$accessCode=$_SESSION['accessCode'];
$file=$_GET['file'];

//handle injection
if($file=="." || $file==".."){$file="";}

if($accessCode=="blahblahblah8"){

    $mediaRoot="../../../hostMedia";
    $file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
    $file=str_replace('\\',"/",$file);

    $filesize = filesize($file);

    $offset = 0;
    $length = $filesize;
    $lastMod=filemtime($file);

    if ( isset($_SERVER['HTTP_RANGE']) ) {
        // if the HTTP_RANGE header is set we're dealing with partial content
        $partialContent = true;
        // find the requested range
        preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
        $offset = intval($matches[1]);
        $length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;
    } else {
        $partialContent = false;
    }


    if ( $partialContent ) {
        // output the right headers for partial content
        header('HTTP/1.1 206 Partial Content');
        header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
    }else{
        header('HTTP/1.1 200 OK');
    }

    // output the regular HTTP headers
    header('Content-Type: video/mp4');
    header('Content-Length: ' . $length);
    header('Accept-Ranges: bytes');
    header('ETag: "3410d79f-576de84c004aa"');
    header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', $lastMod)); 

    // don't forget to send the data too
    $file = fopen($file, 'r');

    // seek to the requested offset, this is 0 if it's not a partial content request
    fseek($file, $offset);

    //populate $data with all except the last byte of the file
    $numBytes=($length);
    $dataLen=0;
    while($dataLen<$numBytes){
        $lenGrab=($numBytes-$dataLen);
        if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
        $data=fread($file, $lenGrab);
        print($data);
        $dataLen+=strlen($data);
    }

    fclose($file);
}else{
    echo "You are not authorized to view this media.";
}

?>

如果有人能让这件事发挥作用,那么你就是个超级英雄!

最终更新(暂时...)

好吧,在经历了很多很多小时的挫折之后,我不得不放弃这种方法并尝试不同的方法。幸运的是,通常有不止一种方法可以完成某事,而我找到了另一种方法。我将 .mp4 文件托管在使用 HTTP Basic Auth 保护的文件夹中的 doc 根目录中。与我试图实现的目标非常相似,它对我有用。感谢您的建议和指导!

【问题讨论】:

    标签: php video-streaming chromecast


    【解决方案1】:

    这个脚本可能是你要找的,它通过 PHP 处理视频服务非常好,Source

     <?php
    // disable zlib so that progress bar of player shows up correctly
    if(ini_get('zlib.output_compression')) {
        ini_set('zlib.output_compression', 'Off'); 
    }
     
    $folder = '.'; 
    $filename = 'video.mp4';
    $path = $folder.'/'.$filename;
     
    // from: http://l...content-available-to-author-only...n.net/post/stream-videos-php/
    if (file_exists($path)) {
        // Clears the cache and prevent unwanted output
        ob_clean();
     
        $mime = "video/mp4"; // The MIME type of the file, this should be replaced with your own.
        $size = filesize($path); // The size of the file
     
        // Send the content type header
        header('Content-type: ' . $mime);
     
        // Check if it's a HTTP range request
        if(isset($_SERVER['HTTP_RANGE'])){
            // Parse the range header to get the byte offset
            $ranges = array_map(
                'intval', // Parse the parts into integer
                explode(
                    '-', // The range separator
                    substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header
                )
            );
     
            // If the last range param is empty, it means the EOF (End of File)
            if(!$ranges[1]){
                $ranges[1] = $size - 1;
            }
     
            // Send the appropriate headers
            header('HTTP/1.1 206 Partial Content');
            header('Accept-Ranges: bytes');
            header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range
     
            // Send the ranges we offered
            header(
                sprintf(
                    'Content-Range: bytes %d-%d/%d', // The header format
                    $ranges[0], // The start range
                    $ranges[1], // The end range
                    $size // Total size of the file
                )
            );
     
            // It's time to output the file
            $f = fopen($path, 'rb'); // Open the file in binary mode
            $chunkSize = 8192; // The size of each chunk to output
     
            // Seek to the requested start range
            fseek($f, $ranges[0]);
     
            // Start outputting the data
            while(true){
                // Check if we have outputted all the data requested
                if(ftell($f) >= $ranges[1]){
                    break;
                }
     
                // Output the data
                echo fread($f, $chunkSize);
     
                // Flush the buffer immediately
                @ob_flush();
                flush();
            }
        }
        else {
            // It's not a range request, output the file anyway
            header('Content-Length: ' . $size);
     
            // Read the file
            @readfile($path);
     
            // and flush the buffer
            @ob_flush();
            flush();
        }
     
    }
    die();
     
    ?>
    

    【讨论】:

      【解决方案2】:

      您的标题“几乎完全相同”,这就是问题所在。使它们相同:P

      使用浏览器上的开发人员工具 (F12) 并检查每个请求发出的网络标头。最可能的原因是我在类似项目中使用的以下几行,而您似乎缺少:

      header('Content-Description: File Transfer');
      header('Content-Disposition: inline; filename=' . basename($file));
      

      或者它可能想要

      header('Content-Disposition: attachment; filename=' . basename($file));
      

      【讨论】:

      • 谢谢,但是当我尝试“header('Content-Disposition: inline; filename=' . basename($file));”时,我收到以下错误:“ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION”。我试图让标题完全一样,唯一我无法得到的是 ETag 的,在做了一些研究之后,我认为这不是问题。
      • 您是在其他地方设置配置吗? (在包含的文件中或上面未发布的某个地方?)
      • 还有你的文件名是什么?我看到 stackoverflow.com/questions/13578428/… 文件名中包含逗号的错误
      • 好建议...你知道另一个标题可能来自哪里吗?我不包括另一个文件(我知道),但你是对的,它是从某个地方获取该标题...
      • 如果你去掉那一行,开发者工具中是否显示标题?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-28
      • 1970-01-01
      • 1970-01-01
      • 2020-09-12
      • 2013-04-29
      • 2014-02-19
      • 1970-01-01
      相关资源
      最近更新 更多