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

问题描述

使用 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.host
    },
    {
      parseReqBody: false, // 去除默认的 body,解决某些播放源 411 问题
      proxyReqPathResolver: function(req) {
        var target = urlParse(decodeURIComponent(req.query.url))
        return target.path
      },
    }
  )
)

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) {
  try {
    var url = decodeURIComponent(req.query.url)
    var parseUrl = urlParse(url)
    var domain = parseUrl.protocol + '//' + parseUrl.host // http 域名地址
    var m3u8Path = url.match(/\S+\//)[0] // http 至最后一个 '/' 字符
    axios
      .get(url)
      .then(resp => {
        var headers = resp.headers
        var content = resp.data
        // 内容为 ts 文件地址则使用 tsProxy 的 path
        var path = /BANDWIDTH/i.test(content)
          ? '/school/videos?url='
          : '/school/tsProxy?url='

        // 头部信息全部返回
        for (var key in headers) {
          res.append(key, headers[key])
        }

        // 对 data 中目标文件字符串做处理
        content = content.replace(/(?:\n)([^# \n]+\.\S+)/g, function(_, match) {
          // 绝对地址,不做任何修改
          if (/^http/.test(match)) {
            return '\n' + path + encodeURIComponent(match)
          }
          // 相对地址:有文件结构的相对地址直接加域名,否则加上带 path 的域名
          return (
            '\n' +
            path +
            encodeURIComponent((/^\//g.test(match) ? domain : m3u8Path) + match)
          )
        })
        res.send(content)
      })
      .catch(e => {
        res.json({
          code: (e.response && e.response.status) || 404,
          message: e.message || '',
          success: false
        })
      })
  } catch (e) {
    res.json({
      code: 400,
      message: 'URI must be not empty!',
      success: false
    })
  }
}
module.exports = controller;

你可能感兴趣的:(使用代理解决跨域直播源m3u8播放问题)