Skip to content

去插广技术文档

Hiram·Wong edited this page Jun 4, 2024 · 1 revision

去广告技术文档

1. 系统概述

本项目设计为M3U8播放地址处理工具,核心功能在于自动检测并移除嵌入在M3U8播放列表内的广告片段,同时兼容处理嵌套的M3U8链接。利用axios进行网络通信以检索M3U8文件,提供无广告的纯净播放体验。

去除网络插播广告虽满足部分用户需求,但需谨慎行事,考虑到可能引发的法律、技术及道德后果。用户在寻求改善在线体验的同时,应选择合法、安全的方式,并尊重内容创作者和平台的权益

2. 关键模块说明

2.1. Array.prototype.toReversed Polyfill

  • 目标: 确保在所有环境下均可使用数组的倒序功能。
  • 价值: 提高代码的跨环境兼容性。

2.2. URL解析与拼接 (resolve, urljoin)

  • 功能: 实现URL的正确解析与片段的拼接,特别是在处理M3U8内相对路径时。
  • 重要性: 确保资源引用的准确无误。

2.3. 广告检测与过滤核心逻辑 (fixAdM3u8Ai)

  • 职责:
    • 智能识别广告: 通过比对播放片段特征识别广告内容。
    • 嵌套处理: 处理M3U8列表中嵌套的其他M3U8链接。
    • URL修正: 转换所有播放片段URL为绝对路径。
  • 技术亮点:
    • 异步数据获取。
    • 精准的广告识别算法。
    • 详细的日志记录。
  • 工作流程:
      1. 输入: 接收用户提供的M3U8播放地址。
      1. 获取M3U8内容: 使用axios执行网络请求。
      1. 基础处理: 清理文本,转换URL为绝对形式。
      1. 广告识别与剔除:
      • 4.1. 分析首尾播放片段,计算相似度。
      • 4.2. 依据阈值移除疑似广告URL。
      1. 递归处理嵌套M3U8(如存在)。
      1. 输出: 返回处理后的纯净M3U8列表。
      1. 日志记录: 全程记录关键步骤和性能指标。

2.4. 遗留问题

  • 匹配精度
  • 同域名广告识别

3. 示例代码

非本人原创代码,如有侵权,联系删除,请勿用于商业用途。

点我查看示例代码
import axios from 'axios';
import logger from '../../../logger';

if (typeof Array.prototype.toReversed !== 'function') {
  Array.prototype.toReversed = function () {
    const clonedList = this.slice();
    // 倒序新数组
    const reversedList = clonedList.reverse();
    return reversedList;
  };
}

const resolve = (from, to) => {
  const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
  if (resolvedUrl.protocol === 'resolve:') {
    const { pathname, search, hash } = resolvedUrl;
    return pathname + search + hash;
  }
  return resolvedUrl.href;
};

/**
 *  url拼接
 * @param fromPath 初始当前页面url
 * @param nowPath 相对当前页面url
 * @returns {*}
 */
const urljoin = (fromPath, nowPath) => {
  fromPath = fromPath || '';
  nowPath = nowPath || '';
  return resolve(fromPath, nowPath);
};

/**
 *  智能对比去除广告。支持嵌套m3u8。只需要传入播放地址
 * @param m3u8_url m3u8播放地址
 * @param headers 自定义访问m3u8的请求头,可以不传
 * @returns {string}
 */
const fixAdM3u8Ai = async (m3u8_url: string, headers: object | null = null) => {
  let ts = new Date().getTime();
  let option = headers ? { headers } : {};

  function b(s1, s2) {
    let i = 0;
    while (i < s1.length) {
      if (s1[i] !== s2[i]) {
        break;
      }
      i++;
    }
    return i;
  }

  function reverseString(str) {
    return str.split('').reverse().join('');
  }

  //log('播放的地址:' + m3u8_url);
  const m3u8_response = await axios.get(m3u8_url, option);
  let m3u8 = m3u8_response.data;
  //log('m3u8处理前:' + m3u8);
  m3u8 = m3u8
    .trim()
    .split('\n')
    .map((it) => (it.startsWith('#') ? it : urljoin(m3u8_url, it)))
    .join('\n');
  //log('m3u8处理后:============:' + m3u8);
  // 获取嵌套m3u8地址
  m3u8 = m3u8.replace(/\n\n/gi, '\n'); //删除多余的换行符
  let last_url = m3u8.split('\n').slice(-1)[0];
  if (last_url.length < 5) {
    last_url = m3u8.split('\n').slice(-2)[0];
  }

  if (last_url.includes('.m3u8') && last_url !== m3u8_url) {
    m3u8_url = urljoin(m3u8_url, last_url);
    logger.info('嵌套的m3u8_url:' + m3u8_url);
    const m3u8_nest_response = await axios.get(m3u8_url, option);
    m3u8 = m3u8_nest_response.data;
  }
  //log('----处理有广告的地址----');
  let s = m3u8
    .trim()
    .split('\n')
    .filter((it) => it.trim())
    .join('\n');
  let ss = s.split('\n');
  //找出第一条播放地址
  let firststr = ss.find((x) => !x.startsWith('#'));
  let maxl = 0; //最大相同字符
  let firststrlen = firststr!.length;
  //log('字符串长度:' + firststrlen);
  let ml = Math.round(ss.length / 2).toString().length; //取数据的长度的位数
  //log('数据条数的长度:' + ml);
  //找出最后一条播放地址
  let maxc = 0;
  let laststr = ss.toReversed().find((x) => {
    if (!x.startsWith('#')) {
      let k = b(reverseString(firststr), reverseString(x));
      maxl = b(firststr, x);
      maxc++;
      if (firststrlen - maxl <= ml + k || maxc > 10) {
        return true;
      }
    }
    return false;
  });
  logger.info('最后一条切片:' + laststr);
  //log('最小相同字符长度:' + maxl);
  let ad_urls: string[] = [];
  for (let i = 0; i < ss.length; i++) {
    let s = ss[i];
    if (!s.startsWith('#')) {
      if (b(firststr, s) < maxl) {
        ad_urls.push(s); // 广告地址加入列表
        ss.splice(i - 1, 2);
        i = i - 2;
      } else {
        ss[i] = urljoin(m3u8_url, s);
      }
    } else {
      ss[i] = s.replace(/URI=\"(.*)\"/, 'URI="' + urljoin(m3u8_url, '$1') + '"');
    }
  }
  logger.info('处理的m3u8地址:' + m3u8_url);
  logger.info('----广告地址----');
  logger.info(ad_urls);
  m3u8 = ss.join('\n');
  //log('处理完成');
  logger.info('处理耗时:' + (new Date().getTime() - ts).toString());
  return m3u8;
};

export { fixAdM3u8Ai };

4. 结论

算法核心十分重要,微小的逻辑错误,可能引发连锁反应,导致误删非广告部分正常内容的关键数据。因此,在面临模棱两可的决策时,应更加谨慎细致,力求在保留数据的完整性与准确性的同时,实现体验的升级。

跨平台桌面端视频资源播放器,免费高颜值!

Clone this wiki locally