Web前端之路

使用代理解决跨域直播源m3u8播放问题

2019-05-21  本文已影响2人  GDUF_XRT

问题描述

使用 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 直播源解析过程为如下:

  1. 请求 m3u8 播放源地址
  2. 服务端返回 m3u8 纯文本索引文件,其中包含各个媒体段的 url
  3. 客户端解析 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;
上一篇 下一篇

猜你喜欢

热点阅读