Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/289.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php Laravel 5 HTTP响应、M4V文件和iOS MPMoviePlayerServiceController_Php_Ios_Iphone_Video Streaming_Laravel 5 - Fatal编程技术网

Php Laravel 5 HTTP响应、M4V文件和iOS MPMoviePlayerServiceController

Php Laravel 5 HTTP响应、M4V文件和iOS MPMoviePlayerServiceController,php,ios,iphone,video-streaming,laravel-5,Php,Ios,Iphone,Video Streaming,Laravel 5,我有一个让我难堪的情况,所以我正在寻找任何我能得到的帮助 我有一个iOS应用程序,它使用MPMoviePlayerViewController播放由Laravel 5站点管理的M4V视频文件 如果直接从Laravel 5/公用文件夹下载视频文件,则视频文件(在iOS中)播放效果非常好。然而,我通常会存储和提供来自Laravel5的Storage Facade的视频文件,因为我最终会使用S3和弹性转码器 这适用于FireFox和QuickTime浏览器插件、VLC以及其他流媒体视频客户端,但不适用

我有一个让我难堪的情况,所以我正在寻找任何我能得到的帮助

我有一个iOS应用程序,它使用MPMoviePlayerViewController播放由Laravel 5站点管理的M4V视频文件

如果直接从Laravel 5/公用文件夹下载视频文件,则视频文件(在iOS中)播放效果非常好。然而,我通常会存储和提供来自Laravel5的Storage Facade的视频文件,因为我最终会使用S3和弹性转码器

这适用于FireFox和QuickTime浏览器插件、VLC以及其他流媒体视频客户端,但不适用于我们的iOS应用程序

据我所知,mpmovieplayervewcontroller对HTTP响应的格式非常挑剔。我试过用streamdresponse,但似乎没用

因此,例如,以下直接从/public文件夹提取文件的URL在iOS中运行良好:

http://172.16.160.1/video_ae9a7da0efa211e4b115f73708c37d67.m4v
但是,如果我使用Laravel5从存储中提取具有此URL的文件,iOS将不会播放它

http://172.16.160.1/api/getfile/f444b190ef5411e4b7068d1890d109e8/video_ae9a7da0efa211e4b115f73708c37d67.m4v
注意:iOS没有提供任何有意义的错误来帮助调试,但我肯定Laravel5是如何做出HTTP响应的

这是我的路线:

Route::get('myapi/getfile/{filename?}', 'APIController@getfile')->where('filename', '(.*)');
这是我的控制器:

    public function getfile($filename)
{
    return $api = API::getfile($filename);
}
这是我的模型:

public static function getfile($filename) {
$file = Storage::disk('local')->get('Files/'.$filename);
return (new Response($file, 200))->header('Content-Type', 'video/mp4');
}
如果我遗漏了任何支持信息,请让我知道,我会发布它。我的下一步可能是设置Wireshark测试台,看看握手是什么样子


提前感谢您的帮助。:-)

看来我对自己的问题有了答案。根本原因是Laravel 5在提供文件服务时不支持HTTP字节范围请求

这篇文章让我走上了正确的道路:

然后我找到了两篇关于做这件事的帖子Laravel 5:

唯一的缺点是我不能使用存储门面直接以流的形式访问文件。因此,此解决方案只能用于位于本地文件系统上的文件

public static function getfile($filename) {

$size = Storage::disk('local')->size('files/'.$filename);
$file = Storage::disk('local')->get('files/'.$filename);
$stream = fopen($storage_home_dir.'files/'.$filename, "r");

$type = 'video/mp4';
$start = 0;
$length = $size;
$status = 200;

$headers = ['Content-Type' => $type, 'Content-Length' => $size, 'Accept-Ranges' => 'bytes'];

if (false !== $range = Request::server('HTTP_RANGE', false)) {
    list($param, $range) = explode('=', $range);
    if (strtolower(trim($param)) !== 'bytes') {
    header('HTTP/1.1 400 Invalid Request');
    exit;
    }
    list($from, $to) = explode('-', $range);
    if ($from === '') {
    $end = $size - 1;
    $start = $end - intval($from);
    } elseif ($to === '') {
    $start = intval($from);
    $end = $size - 1;
    } else {
    $start = intval($from);
    $end = intval($to);
    }
    $length = $end - $start + 1;
    $status = 206;
    $headers['Content-Range'] = sprintf('bytes %d-%d/%d', $start, $end, $size);
}

return Response::stream(function() use ($stream, $start, $length) {
    fseek($stream, $start, SEEK_SET);
    echo fread($stream, $length);
    fclose($stream);
    }, $status, $headers);
}

我知道这是一篇老文章,但我最终需要用Laravel从S3向需要HTTP_范围支持的播放器传输视频。我把这些放在一起(在读了很多文章之后)。它应该支持您在Laravel中定义的所有磁盘()

namespace Http\Responses;

use Illuminate\Http\Request;
use Storage;

class S3FileStream
{
    /**
     * @var \League\Flysystem\AwsS3v3\AwsS3Adapter
     */
    private $adapter;

    /**
     * @var \Aws\S3\S3Client
     */
    private $client;

    /**
     * @var file end byte
     */
    private $end;

    /**
     * @var string
     */
    private $filePath;

    /**
     * @var bool storing if request is a range (or a full file)
     */
    private $isRange = false;

    /**
     * @var length of bytes requested
     */
    private $length;

    /**
     * @var
     */
    private $return_headers = [];

    /**
     * @var file size
     */
    private $size;

    /**
     * @var start byte
     */
    private $start;

    /**
     * S3FileStream constructor.
     * @param string $filePath
     * @param string $adapter
     */
    public function __construct(string $filePath, string $adapter = 's3')
    {
        $this->filePath   = $filePath;
        $this->filesystem = Storage::disk($adapter)->getDriver();
        $this->adapter    = Storage::disk($adapter)->getAdapter();
        $this->client     = $this->adapter->getClient();
    }

    /**
     * Output file to client.
     */
    public function output()
    {
        return $this->setHeaders()->stream();
    }

    /**
     * Output headers to client.
     * @return $this
     */
    protected function setHeaders()
    {
        $object = $this->client->headObject([
            'Bucket' => $this->adapter->getBucket(),
            'Key'    => $this->filePath,
        ]);

        $this->start = 0;
        $this->size  = $object['ContentLength'];
        $this->end   = $this->size - 1;
        //Set headers
        $this->return_headers                        = [];
        $this->return_headers['Last-Modified']       = $object['LastModified'];
        $this->return_headers['Accept-Ranges']       = 'bytes';
        $this->return_headers['Content-Type']        = $object['ContentType'];
        $this->return_headers['Content-Disposition'] = 'inline; filename=' . basename($this->filePath);

        if (!is_null(request()->server('HTTP_RANGE'))) {
            $c_start = $this->start;
            $c_end   = $this->end;

            [$_, $range] = explode('=', request()->server('HTTP_RANGE'), 2);
            if (strpos($range, ',') !== false) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);

                return response('416 Requested Range Not Satisfiable', 416);
            }
            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) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);

                return response('416 Requested Range Not Satisfiable', 416);
            }
            $this->start                            = $c_start;
            $this->end                              = $c_end;
            $this->length                           = $this->end - $this->start + 1;
            $this->return_headers['Content-Length'] = $this->length;
            $this->return_headers['Content-Range']  = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
            $this->isRange                          = true;
        } else {
            $this->length                           = $this->size;
            $this->return_headers['Content-Length'] = $this->length;
            unset($this->return_headers['Content-Range']);
            $this->isRange = false;
        }

        return $this;
    }

    /**
     * Stream file to client.
     * @throws \Exception
     */
    protected function stream()
    {
        $this->client->registerStreamWrapper();
        // Create a stream context to allow seeking
        $context = stream_context_create([
            's3' => [
                'seekable' => true,
            ],
        ]);
        // Open a stream in read-only mode
        if (!($stream = fopen("s3://{$this->adapter->getBucket()}/{$this->filePath}", 'rb', false, $context))) {
            throw new \Exception('Could not open stream for reading export [' . $this->filePath . ']');
        }
        if (isset($this->start)) {
            fseek($stream, $this->start, SEEK_SET);
        }

        $remaining_bytes = $this->length ?? $this->size;
        $chunk_size      = 1024;

        $video = response()->stream(
            function () use ($stream, $remaining_bytes, $chunk_size) {
                while (!feof($stream) && $remaining_bytes > 0) {
                    echo fread($stream, $chunk_size);
                    $remaining_bytes -= $chunk_size;
                    flush();
                }
                fclose($stream);
            },
            ($this->isRange ? 206 : 200),
            $this->return_headers
        );

        return $video;
    }
}
我使用了下面的类,位于App/Http/Responses。要使用此类,请创建一个执行此操作的方法(这将是getFile方法的内容):

我只是指出我的视频播放器的src在一个路线,为这种方法和成功

S3FileStream.php:
namespace Http\Responses;

use Illuminate\Http\Request;
use Storage;

class S3FileStream
{
    /**
     * @var \League\Flysystem\AwsS3v3\AwsS3Adapter
     */
    private $adapter;

    /**
     * @var \Aws\S3\S3Client
     */
    private $client;

    /**
     * @var file end byte
     */
    private $end;

    /**
     * @var string
     */
    private $filePath;

    /**
     * @var bool storing if request is a range (or a full file)
     */
    private $isRange = false;

    /**
     * @var length of bytes requested
     */
    private $length;

    /**
     * @var
     */
    private $return_headers = [];

    /**
     * @var file size
     */
    private $size;

    /**
     * @var start byte
     */
    private $start;

    /**
     * S3FileStream constructor.
     * @param string $filePath
     * @param string $adapter
     */
    public function __construct(string $filePath, string $adapter = 's3')
    {
        $this->filePath   = $filePath;
        $this->filesystem = Storage::disk($adapter)->getDriver();
        $this->adapter    = Storage::disk($adapter)->getAdapter();
        $this->client     = $this->adapter->getClient();
    }

    /**
     * Output file to client.
     */
    public function output()
    {
        return $this->setHeaders()->stream();
    }

    /**
     * Output headers to client.
     * @return $this
     */
    protected function setHeaders()
    {
        $object = $this->client->headObject([
            'Bucket' => $this->adapter->getBucket(),
            'Key'    => $this->filePath,
        ]);

        $this->start = 0;
        $this->size  = $object['ContentLength'];
        $this->end   = $this->size - 1;
        //Set headers
        $this->return_headers                        = [];
        $this->return_headers['Last-Modified']       = $object['LastModified'];
        $this->return_headers['Accept-Ranges']       = 'bytes';
        $this->return_headers['Content-Type']        = $object['ContentType'];
        $this->return_headers['Content-Disposition'] = 'inline; filename=' . basename($this->filePath);

        if (!is_null(request()->server('HTTP_RANGE'))) {
            $c_start = $this->start;
            $c_end   = $this->end;

            [$_, $range] = explode('=', request()->server('HTTP_RANGE'), 2);
            if (strpos($range, ',') !== false) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);

                return response('416 Requested Range Not Satisfiable', 416);
            }
            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) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);

                return response('416 Requested Range Not Satisfiable', 416);
            }
            $this->start                            = $c_start;
            $this->end                              = $c_end;
            $this->length                           = $this->end - $this->start + 1;
            $this->return_headers['Content-Length'] = $this->length;
            $this->return_headers['Content-Range']  = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
            $this->isRange                          = true;
        } else {
            $this->length                           = $this->size;
            $this->return_headers['Content-Length'] = $this->length;
            unset($this->return_headers['Content-Range']);
            $this->isRange = false;
        }

        return $this;
    }

    /**
     * Stream file to client.
     * @throws \Exception
     */
    protected function stream()
    {
        $this->client->registerStreamWrapper();
        // Create a stream context to allow seeking
        $context = stream_context_create([
            's3' => [
                'seekable' => true,
            ],
        ]);
        // Open a stream in read-only mode
        if (!($stream = fopen("s3://{$this->adapter->getBucket()}/{$this->filePath}", 'rb', false, $context))) {
            throw new \Exception('Could not open stream for reading export [' . $this->filePath . ']');
        }
        if (isset($this->start)) {
            fseek($stream, $this->start, SEEK_SET);
        }

        $remaining_bytes = $this->length ?? $this->size;
        $chunk_size      = 1024;

        $video = response()->stream(
            function () use ($stream, $remaining_bytes, $chunk_size) {
                while (!feof($stream) && $remaining_bytes > 0) {
                    echo fread($stream, $chunk_size);
                    $remaining_bytes -= $chunk_size;
                    flush();
                }
                fclose($stream);
            },
            ($this->isRange ? 206 : 200),
            $this->return_headers
        );

        return $video;
    }
}

感谢您的分享,也许标题应该扩展为“内容传输编码”=>“二进制”、“内容处置”=>“附件”;filename=“”.$filename.”“非常感谢。在花了2个多小时试图从Laravel+1获得一个iPhone视频在Safari(桌面)中显示后,我节省了很多时间。哇,太谢谢你了。这帮助我解决了我几天来一直遇到的问题:如果在Safari中使用此代码,那么它将无法正常工作。原因是$headers=['Content Length'=>$size];因为您已将该大小固定为总文件大小。因此,第二个回答令人困惑。如果您对iPhone(Safari)有相同的问题,请删除$size。