Remove Ad from M3U8

拦截m3u8请求并移除其中插入的广告ts片段

  1. // ==UserScript==
  2. // @name Remove Ad from M3U8
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description 拦截m3u8请求并移除其中插入的广告ts片段
  6. // @author
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11.  
  12.  
  13. (function() {
  14. 'use strict';
  15. let processingM3U8 = false;
  16.  
  17. async function fixAdM3u8Ai(m3u8_url, headers = null){
  18. let ts = new Date().getTime();
  19. let option = headers ? {headers: headers} : {};
  20.  
  21. function b(s1, s2) {
  22. let i = 0;
  23. while (i < s1.length) {
  24. if (s1[i] !== s2[i]) {
  25. break
  26. }
  27. i++
  28. }
  29. return i;
  30. }
  31.  
  32. function reverseString(str) {
  33. return str.split('').reverse().join('');
  34. }
  35.  
  36. //log('播放的地址:' + m3u8_url);
  37. const m3u8_response = await fetch(m3u8_url, option);
  38. let m3u8 =await m3u8_response.text();
  39. //log('m3u8处理前:' + m3u8);
  40. m3u8 = m3u8.trim().split('\n').map(it => it.startsWith('#') ? it : urljoin(m3u8_url, it)).join('\n');
  41. //log('m3u8处理后:============:' + m3u8);
  42. // 获取嵌套m3u8地址
  43. m3u8 = m3u8.replace(/\n\n/gi, '\n'); //删除多余的换行符
  44. let last_url = m3u8.split('\n').slice(-1)[0];
  45. if (last_url.length < 5) {
  46. last_url = m3u8.split('\n').slice(-2)[0];
  47. }
  48.  
  49. if (last_url.includes('.m3u8') && last_url !== m3u8_url) {
  50. m3u8_url = urljoin(m3u8_url, last_url);
  51. // console.log('嵌套的m3u8_url:' + m3u8_url);
  52. const m3u8_nest_response = await fetch(m3u8_url, option);
  53. m3u8 = await m3u8_nest_response.text();
  54. }
  55. //log('----处理有广告的地址----');
  56. let s = m3u8.trim().split('\n').filter(it => it.trim()).join('\n');
  57. let ss = s.split('\n');
  58. //找出第一条播放地址
  59. //let firststr = ss.find(x => !x.startsWith('#'));
  60. let firststr = '';
  61. let maxl = 0;//最大相同字符
  62. let kk = 0;
  63. let kkk = 2;
  64. let secondstr = '';
  65. for (let i = 0; i < ss.length; i++) {
  66. let s = ss[i];
  67. if (!s.startsWith("#")) {
  68. if (kk == 0) firststr = s;
  69. if (kk == 1) maxl = b(firststr, s);
  70. if (kk > 1) {
  71. if (maxl > b(firststr, s)) {
  72. if (secondstr.length < 5) secondstr = s;
  73. kkk = kkk + 2;
  74. } else {
  75. maxl = b(firststr, s);
  76. kkk++;
  77. }
  78. }
  79. kk++;
  80. if (kk >= 20) break;
  81. }
  82. }
  83. if (kkk > 30) firststr = secondstr;
  84. let firststrlen = firststr != null ? firststr.length : null
  85. //log('字符串长度:' + firststrlen);
  86. let ml = Math.round(ss.length / 2).toString().length; //取数据的长度的位数
  87. //log('数据条数的长度:' + ml);
  88. //找出最后一条播放地址
  89. let maxc = 0;
  90. let laststr = ss.toReversed().find((x) => {
  91. if (!x.startsWith('#')) {
  92. let k = b(reverseString(firststr), reverseString(x));
  93. maxl = b(firststr, x);
  94. maxc++;
  95. if (firststrlen - maxl <= ml + k || maxc > 10) {
  96. return true;
  97. }
  98. }
  99. return false;
  100. });
  101. // console.log('最后一条切片:' + laststr);
  102. //log('最小相同字符长度:' + maxl);
  103. let ad_urls = [];
  104. for (let i = 0; i < ss.length; i++) {
  105. let s = ss[i];
  106. if (!s.startsWith('#')) {
  107. if (b(firststr, s) < maxl) {
  108. ad_urls.push(s); // 广告地址加入列表
  109. ss.splice(i - 1, 2);
  110. i = i - 2;
  111. } else {
  112. ss[i] = urljoin(m3u8_url, s);
  113. }
  114. } else {
  115. ss[i] = s.replace(/URI=\"(.*)\"/, 'URI="' + urljoin(m3u8_url, '$1') + '"');
  116. }
  117. }
  118. // console.log('处理的m3u8地址:' + m3u8_url);
  119. // console.log('----广告地址----');
  120. console.log(ad_urls);
  121. m3u8 = ss.join('\n');
  122. //log('处理完成');
  123. //console.log('处理耗时:' + (new Date().getTime() - ts).toString());
  124. return m3u8;
  125. }
  126.  
  127.  
  128. function resolve (from, to){
  129. const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
  130. if (resolvedUrl.protocol === 'resolve:') {
  131. const { pathname, search, hash } = resolvedUrl;
  132. return pathname + search + hash;
  133. }
  134. return resolvedUrl.href;
  135. };
  136.  
  137.  
  138. function urljoin (fromPath, nowPath){
  139. fromPath = fromPath || '';
  140. nowPath = nowPath || '';
  141. return resolve(fromPath, nowPath);
  142. };
  143.  
  144. function playm3u8(urlm3,playerElement) {
  145. if (Hls.isSupported()) {
  146.  
  147. var hls = new Hls();
  148. hls.loadSource(urlm3);
  149. hls.attachMedia(playerElement);
  150. //video.play();
  151. hls.on(Hls.Events.MANIFEST_PARSED, function () {
  152. playerElement.play();
  153. });
  154. hls.on(Hls.Events.ERROR, (event, data) => {
  155. //console.log("1222");
  156. console.log(event, data);
  157. // 监听出错事件
  158. });
  159. }
  160. }
  161.  
  162.  
  163. function replacePlayerSource(playerElement, newSource) {
  164.  
  165. // 假设播放器使用 <video> 或类似标签
  166.  
  167. if (playerElement) {
  168. playm3u8(newSource,playerElement)
  169.  
  170. } else {
  171.  
  172. console.warn('Player element not found.');
  173.  
  174. }
  175.  
  176. }
  177.  
  178. const originalXhrOpen = XMLHttpRequest.prototype.open;
  179.  
  180. XMLHttpRequest.prototype.open = function(method, url) {
  181.  
  182. if (url.endsWith('.m3u8')) {
  183. this.addEventListener('load', async function() {
  184.  
  185. if (!processingM3U8 && (this.responseType === '' || this.responseType === 'text')) {
  186. processingM3U8 = true;
  187. const m3u8_content = await fixAdM3u8Ai(url);
  188. processingM3U8 = false;
  189.  
  190. // 创建 Blob 并生成 URL
  191.  
  192. const blob = new Blob([m3u8_content], { type: 'text/plain' });
  193. const newM3u8Url = URL.createObjectURL(blob);
  194. // 替换播放器源
  195. const player = document.querySelector('video'); // 根据实际情况调整选择器
  196. replacePlayerSource(player,newM3u8Url);
  197.  
  198. }
  199.  
  200. });
  201.  
  202. }
  203.  
  204. originalXhrOpen.apply(this, arguments);
  205.  
  206. };
  207.  
  208.  
  209.  
  210. })();
  211.  
  212.