S3に保存されたmp4動画をPHPを経由して配信していたのだが、iOS/MacのSafariだと再生できない事が判明した。
原因はVideoタグなどのリソースはRangeヘッダー
を付けてリクエストが投げられるのだが、ここで正しく206 Partial Content
を返してあげないとSafariでは動画を再生できないというものだった。
chromeとかは再生してくれるのにね。流石Safari。すごいぞSafari。滅びればいいのに。
で、以下どうやって対策をとったかという話。
環境
Service | Version |
---|---|
PHP | 7.2.2 |
Laravel | 6.5.0 |
問題のコード
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; class ResourceController extends Controller { public function download() { return Storage::disk('s3')->download('your/file/path/movie.mp4', 'movie.mp4'); } }
対応
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Request; use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\StreamedResponse; class ResourceController extends Controller { public function download() { $disk = Storage::disk("s3"); $size = $disk->size('your/file/path/movie.mp4'); $start = 0; $status = 200; $length = $size; $headers = [ 'Content-Type' => 'video/mp4', 'Content-Length' => $size, 'Accept-Ranges' => 'bytes' ]; if ($range = Request::server('HTTP_RANGE', false)) { list($param, $range) = explode('=', $range); if (strtolower(trim($param)) !== 'bytes') { abort(400); } list($from, $to) = explode('-', $range); if ($from === '') { $end = $size - 1; $start = $end - intval($from); } else if ($to === '') { $start = intval($from); $end = $size - 1; } else { $start = intval($from); $end = intval($to); } $length = $end - $start + 1; $headers['Content-Length'] = $length; $headers['Content-Range'] = sprintf('bytes %d-%d/%d', $start, $end, $size); $status = 206; } $response = new StreamedResponse(function() use ($disk, $start, $length) { $stream = $disk->readStream('your/file/path/movie.mp4'); fseek($stream, $start, SEEK_SET); echo fread($stream, $length); fclose($stream); }, $status, $headers); $response->headers->set( 'Content-Disposition', $response->headers->makeDisposition( 'attachment', "movie.mp4", str_replace('%', '', Str::ascii("movie.mp4")) ) ); return $response; } }
上記は実際に書いたコードを何とか一つにまとめたものです。
動作確認はしてないです。(もちろん上記のようにまとめる前の本チャンコードでは動作確認取れてます。)
まあこんな感じ。
めんどくさ。