在 PHP 中将文件流式传输到 html5 而不将文件写入文件系统

Filestreaming to html5 in PHP without writing a file to the filesystem

我是 PHP 的新手,而且绝对是流媒体视频的新手。

我的 SQL 服务器的文件流列中存储了 mp4 视频,我想将它们流式传输到 HTML5 视频。

我通过 'file_put_contents($filePath, $queryresult),' 为它提供一个临时文件,设法使用了以下流 class ( http://codesamplez.com/programming/php-html5-video-streaming-tutorial ),但不得不依赖于写入然后清理一个中间文件文件系统中的文件最终有问题。

我是否遗漏了一个关键概念,它允许我将文件直接从数据库流式传输到视频 "src" 而无需在两者之间写入文件?

提前致谢!

如果您自己创建临时文件然后将其删除。然后您可以使用 PHP 的内置功能改进流程 tempfile() 其中

Creates a temporary file with a unique name in read-write (w+) mode and returns a file handle .The file is automatically removed when closed (for example, by calling fclose(), or when there are no remaining references to the file handle returned by tmpfile()), or when the script ends.

private function open()
{
    if ( !($this->stream = tmpfile()) ) {
        fwrite($this->stream, $videodata);
        rewind($this->stream);
        die('Could not open stream for reading');
    }
    // now your temp file is ready to be read.
} 

所以现在你没有临时的责任。删除 PHP 将处理 you.If 您需要自定义温度的问题。文件名或获取详细信息,您可以使用此 http://php.net/manual/en/function.sys-get-temp-dir.php

如果您根本不想使用文件,而只想使用内存解决方案,那么您可以尝试使用内存流

private function open()
{
    if ( !($this->stream = fopen('php://memory', 'wb+') ) ) {
        fwrite($this->stream, $videodata);
        rewind($this->stream);
        die('Could not open stream for reading');
    }
    // now your in-memory file is ready to be read.
} 

您可以在内存流上进行 fread、fwrite、file_get_contents 或使用 tcp 流将其扔到网络上。但我必须说第二种解决方案有点占用内存,因此不适合流式传输大文件。

写临时文件对我来说似乎是一种临时解决方法。由于您正在从数据库获取流并且需要向客户端提供流,因此根本不需要将数据存储在硬盘上。

您需要做的就是替换链接示例中从文件中读取的部分(并将二进制内容回显给客户端)并改为从 sql 中读取。

在您链接的示例函数 stream() 中,替换这部分

    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;
    }

用这样的东西替换:

/* Execute the query. */  
$stmt = sqlsrv_query($conn, $tsql, $params);  
if( $stmt === false )  
{  
     echo "Error in statement execution.</br>";  
     die( print_r( sqlsrv_errors(), true));  
}  

/* Retrieve and display the data.  
The return data is retrieved as a binary stream. */  
if ( sqlsrv_fetch( $stmt ) )  
{  
   $videostream = sqlsrv_get_field( $stmt, 0,   
                      SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));  
   //header("Content-Type: image/jpg");  
   fpassthru($videostream );  
}  
else  
{  
     echo "Error in retrieving data.</br>";  
     die(print_r( sqlsrv_errors(), true));  
}  

为了后代,这是我的最终结果,它似乎不需要临时文件就可以正常工作。

不确定将查询包含在 class 中并仅将 $MediaFileID 传递给 class 是否更有意义,但它按原样工作,所以现在我已经离开了。

整个修改后的 class 包含在其原始信用信息中:

function ExecutereadMediaSP($MediaFileID){

    try{

    $connection = ConnectToDB();

    ini_set('memory_limit', '-1');

    // logs basic info about media viewer.  Mainly for a basic hit counter.
    LogMediaRequest($connection, $MediaFileID);

    //Selects binary data from SQL Server based on MediaFileID.  Passes this to Stream Class.  May be able to make further improvements later.
    $sql = "SELECT ... data from FILESTREAM column based on file ID ...";   

    $rst = $connection->prepare($sql);
    $rst->execute();
    $rst->bindColumn(1, $filecontent, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);

    $row = $rst->fetch(PDO::FETCH_ASSOC);//sql can only return one row due to unique identifier

    //Stream file 
    $stream = new VideoStream($filecontent);
    $stream->start();

    //Clean up.
    $rst->closeCursor();
    unset($rst);
    $connection = null;

    } catch (Exception $e) {
        error_log("Error in getting video\n".$e->getMessage(),0);
    }

}


/**
 * VideoStream - PHP class that supports (adaptive) streaming of files
 *
 * @author Rana
 * modified by HazCod to use stream_get_contents and correct session shutoff
 * https://github.com/HazCod
 * @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
 */
class VideoStream
{
    private $path = "";
    private $stream = "";
    private $buffer = 102400;
    private $start  = -1;
    private $end    = -1;
    private $size   = 0;

    function __construct($filecontent) 
    {


        try{

        // Opens file handle resource to replace use of actual file in the file system.
        $file_handle = fopen('php://memory', 'r+', false, stream_context_create()); 

        // Writes data from SQL Query to file wrapper.
        fwrite($file_handle, $filecontent);

        // Moves pointer to beginning of file.
        fseek($file_handle, 0);

        // Gets info on the "file."  Required to get filesize.
        $fstat = array();

        // gather statistics
        $fstat = fstat($file_handle);

        //Set File Size for Stream Class.
        $this->size  = $fstat['size'];

        // Define Stream as "File."
        $this->stream = $file_handle;

        } catch (PDOException $e) {
            error_log("Error in getting video\n".$e->getMessage(),0);
        }

    }

    /**
     * Set proper header to serve the video content
     */
    private function setHeader()
    {   
        ob_get_clean();
        header("Content-Type: video/mp4");
        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' );
        $this->start = 0;

        $this->end   = $this->size - 1;
       // 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 && connection_aborted() == 0) {
            $bytesToRead = $this->buffer;
            if(($i+$bytesToRead) > $this->end) {
                $bytesToRead = $this->end - $i + 1;
            }
            $data = stream_get_contents($this->stream, $bytesToRead);
            echo $data;
            flush();
            $i += $bytesToRead;
        }
    }

    /**
     * Start streaming video content
     */
    function start()
    {
        session_write_close(); //ensure our session is written away before streaming, else we cannot use it elsewhere
        $this->setHeader();
        $this->stream();
        $this->end();
    }
}