问题描述
使用 videojs 播放 cctv 直播源(http://cctvcnch5c.v.wscdns.com/live/cctv13_2/index.m3u8)时,出现跨域问题,如下所示:
Access to XMLHttpRequest at 'http://cctvcnch5c.v.wscdns.com/live/cctv13_2/index.m3u8' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
解决思路
跨域限制在 cctvcnch5c.v.wscdns.com
上,无法通过配置服务器解决限制。只能通过中间层接口请求或代理进行绕过(服务器访问不受跨域协议限制)。
解决过程
中间层使用 express 框架,代理插件可使用 express-http-proxy
。
m3u8 直播源解析过程为如下:
- 请求 m3u8 播放源地址
- 服务端返回 m3u8 纯文本索引文件,其中包含各个媒体段的 url
- 客户端解析 m3u8 的播放列表,再按序请求每一段的url,获取ts数据流
其中,服务端返回的媒体段地址为相对地址,默认前缀路径为步骤1的 m3u8 的请求路径,所以还需要对步骤1返回的数据做处理,即步骤1需要使用接口模式,而步骤3使用代理模式(express-http-proxy
)。
具体代码如下:
// main.js
var express = require('express');
var proxy = require('express-http-proxy');
var router = express.Router();
var urlParse = require('url').parse;
var controller = require('./controller.js');
// 代理直播源
router.get('/videos', controller.videoProxy);
router.use(
'/tsProxy',
proxy(
function(req) {
var target = urlParse(decodeURIComponent(req.query.url))
return target.hostname
},
{
parseReqBody: false, // 去除默认的 body,解决某些播放源 411 问题
proxyReqPathResolver: function(req) {
var target = urlParse(decodeURIComponent(req.query.url))
return target.pathname
},
}
)
)
express-http-proxy
默认自动解析并设置 req.body,这将导致 cctv 服务器识别到没有 content-length 头部的 request body,并返回 411 错误码。所以需要在中间件配置上将 parseReqBody 设置为false。
// controller.js
var axios = require('axios')
var controller = {};
controller.videoProxy = function (req, res) {
const url = decodeURIComponent(req.query.url);
const m3u8Path = url.match(/\S+\//)[0]; // http 至最后一个 '/' 之间字符
axios.get(url)
.then(resp => {
var headers = resp.headers;
var content = resp.data;
for (var key in headers) {
res.append(key, headers[key]);
}
// 对 data 中目标文件字符串做处理
content = content.replace(/\S+\.ts\S{0,}/g, function (match) {
// "/" 开头
if (/^\//g.test(match)) {
var targetPathReg = /\/\S+\//g;
// 目标路径前缀与 path 后缀相同,去除目标路径前缀
if (m3u8Path.indexOf(match.match(targetPathReg)[0]) > 0) {
match = match.replace(targetPathReg, '')
} else {
match = match.slice(1)
}
}
return '/tsProxy?url=' + encodeURIComponent(m3u8Path + match)
})
res.send(content);
})
.catch(e => {
res.json({
code: (e.response && e.response.status) || 404,
message: e.message || '',
success: false
});
})
}
module.exports = controller;
网友评论