【问题标题】:Filestreaming to html5 in PHP without writing a file to the filesystem在 PHP 中将文件流式传输到 html5 而无需将文件写入文件系统
【发布时间】:2018-07-20 00:31:53
【问题描述】:

我对 PHP 还很陌生,对流媒体视频也很陌生。

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

我已经设法使用以下流类 (http://codesamplez.com/programming/php-html5-video-streaming-tutorial),通过“file_put_contents($filePath, $queryresult)”为其提供一个临时文件,但必须依赖于写入然后清理中间文件文件系统到底有问题。

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

提前致谢!

【问题讨论】:

  • 我无法访问:codesamplez.com
  • 点击链接好像没问题...
  • 我只需要更改代码示例并删除 stream() 函数中从文件中读取的部分,然后将其替换为从 sql 流中读取...

标签: php video streaming


【解决方案1】:

如果您自己创建临时文件然后将其删除。然后你可以通过使用 PHP 的内置来改进这个过程 tempfile()哪个

在读写 (w+) 模式下创建一个具有唯一名称的临时文件并返回一个文件句柄。该文件在关闭时自动删除(例如,通过调用 fclose(),或者当没有剩余对tmpfile()) 或脚本结束时返回的文件句柄。

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 将为您处理。如果您需要自定义临时。文件名或获取详细信息,您可以使用此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 流将其扔到网络上。但我必须说,第二种解决方案有点占用内存,因此不适合流式传输大文件。

【讨论】:

  • 天哪,甜!我仍然想尝试在没有文件的情况下做到这一点,但这可能会解决我的主要问题,即取消链接似乎并不总是有效,并且 IE 似乎触发了三个要创建的文件(GRRR) ,这可能会使我的服务器崩溃。哈哈!至于流,我会继续努力并感谢您的提示!
  • 是的,我使用了第一个解决方案,它运行良好。第二个解决方案只有在您流式传输只有几 Mbs 的小文件时才好,因为所有数据都加载到 RAM 中并交换;较大的文件会使您的交换文件(假设您有交换文件)很快耗尽。如果您没有交换文件,那么所有东西都会单独加载到 RAM 中......很快您会看到内存不足错误和服务器崩溃
  • 好的,所以我已经成功使用了你的临时文件进程,但它仍然给我一个问题,我在任何地方都看不到这种行为的例子.. 仅在 IE 中,就像以前一样我会浏览到该页面,它会创建一个找到的视频的多个文件,并且不会将它们全部取消链接,即使现在使用临时文件,当浏览到该页面时,每次都会创建三个临时文件,并且它们不会被删除最后,所以他们坐在临时目录中,直到我手动清除它们。就像 IE 在视频标签中看到了引用,并多次点击它而无需点击
  • 奇怪...我认为这可能是由于 IE 不支持每个搜索操作的范围请求 IE 可能会产生新的 php 进程而不是使用相同的进程。顺便说一句,它是哪个版本?
  • IE 11。这似乎至少是问题的一部分。一旦 IE 看​​到视频标签,它就会点击它,从而触发文件创建。 stackoverflow.com/questions/47639381/… 但我也怀疑它在此过程中向链接发出多个请求是出于某种原因,这导致创建多个文件,甚至在用户点击观看视频之前。
【解决方案2】:

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

您需要做的就是替换链接示例中从文件读取的部分(并将二进制内容回显到客户端)并改为从 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));  
}  

【讨论】:

    【解决方案3】:

    为了后代,这是我的最终结果,它似乎在不需要临时文件的情况下运行良好。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-25
      • 2020-02-17
      相关资源
      最近更新 更多