我使用 AWS CloudFront 作为 CDN 向我的网站(Laravel 和 Vue)提供一些视频。我已将我的 CloudFront 设置为使用签名 cookie 从我的 S3 存储桶获取内容,这看起来工作正常,因为我可以看到我的所有 .ts 文件都在我的网络选项卡中获取。
但是问题是,当视频播放时,它会在应该切换片段的时候崩溃,我得到的错误是“媒体播放由于损坏问题或因为媒体使用的功能您的浏览器不支持而中止。 '。我可以跳到视频的另一部分,它将播放直到该片段结束并需要切换(这告诉我我的网站正在获取片段)。
我还使用我的网站来代理 cdn,因此我向 mysite.com/proxyVideo/VideoFileName.m3u8 发出请求,这使用 cookie 签署请求,然后使用 HTTPClient 获取 cdn。下面是我的 Vue 组件
<template>
<div>
<video ref="videoPlayer" id="my-video" class="video-js vjs-default-skin" controls preload="auto" width="640"
height="360">
<source src="mySite.com/proxyVideo/someplaylist.m3u8" type="application/x-mpegURL" />
</video>
</div>
</template>
<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
export default {
mounted() {
// Initialize Video.js player
this.player = videojs(this.$refs.videoPlayer, {
autoplay: true,
controls: true,
preload: 'auto',
techOrder: ['html5'], // Ensure HTML5 is being used
sources: [{
src: 'mySite.com/proxyVideo/someplaylist.m3u8',
type: 'application/x-mpegURL' // MIME type for HLS
}]
});
// Wait until the player is ready
this.player.ready(() => {
// Directly access the player's tech
const hlsTech = this.player.tech({
IWillNotUseThisInPlugins: true
});
if (hlsTech && hlsTech.vhs) {
// Extend Video.js XHR to add Range header for .ts files
hlsTech.vhs.xhr = (options, callback) => {
// Set the Range header if the request is for .ts files
if (options.uri.endsWith('.ts')) {
options.headers = options.headers || {};
options.headers.Range = 'bytes=0-'; // Adjust as needed
}
// Call the original xhr method
return videojs.Vhs.xhr.call(hlsTech.vhs, options, callback);
};
} else {
console.error("HLS is not available on this player instance.");
}
});
},
beforeDestroy() {
if (this.player) {
this.player.dispose();
}
},
};
</script>
这是我的 Laravel 路线
Route::any('/proxyVideo/{path}', function(Request $req, $path) {
$client = new HttpClient();
try {
// Fetch cookies and format for forwarding
$cookies = $req->cookies->all();
$cookieHeader = '';
foreach ($cookies as $name => $value) {
$cookieHeader .= "{$name}={$value}; ";
}
$cookieHeader = rtrim($cookieHeader, '; ');
$headers = [
'Cookie' => $cookieHeader,
'Accept-Ranges' => 'bytes', // Add this to indicate partial support
];
if ($req->hasHeader('Range')) {
$headers['Range'] = $req->header('Range');
}
// Make the request to CloudFront with cookies and headers
$response = $client->request($req->method(), "cdn.cloudfront.net/{$path}", [
'headers' => $headers
]);
// Handle response and set status to 206 if Range was requested
$httpFoundationFactory = new HttpFoundationFactory();
$symfonyResponse = $httpFoundationFactory->createResponse($response);
$headers = $symfonyResponse->headers->all();
$content = $symfonyResponse->getContent();
$statusCode = $symfonyResponse->getStatusCode();
// Force 206 if Range is requested but response returns 200
if ($req->hasHeader('Range') && $statusCode === 200) {
$statusCode = 206;
}
return response($content, $statusCode)
->withHeaders($headers)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, OPTIONS')
->header('Access-Control-Allow-Headers', 'Range, Content-Type, Authorization')
->header('Access-Control-Expose-Headers', 'Content-Length, Content-Range');
} catch (\Exception $e) {
return response()->json(['error' => 'Unable to proxy request', 'message' => $e->getMessage()], 502);
}
})->where('path', '.*');
这是我的 .m3u8 文件的示例
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.400000,
video-6724a1ce2ef27.mp4_0_1000_00000.ts
#EXTINF:9.600000,
video-6724a1ce2ef27.mp4_0_1000_00001.ts
#EXTINF:10.400000,
video-6724a1ce2ef27.mp4_0_1000_00002.ts
#EXTINF:1.550000,
video-6724a1ce2ef27.mp4_0_1000_00003.ts
#EXT-X-ENDLIST
如果我使用在线测试 .m3u8 文件,它确实可以工作。我正在测试的流是
https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8
这显然没有使用我的代理,所以我猜测它与此有关,但不确定。 .m3u8 文件请求结果为 200,我认为是正确的,.ts 文件有 206,我再次认为是正确的
我也在使用我的网站来代理 CDN
这完全违背了CDN的目的。 重点是客户端应该直接连接到它。
// Force 206 if Range is requested but response returns 200
不,不要这样做! 如果没有关联的响应标头,您的
206
对客户端来说毫无意义。
不要做任何这些事。 停止代理的东西。 让您的客户端直接连接到 CloudFront。