重构omofuns的视频播放,去除插播广告,播完自动下集、快捷键n键下集f键全屏、播放进度保存

2025/7/12 16:39:37

// ==UserScript==
// @name        重构omofuns的视频播放,去除插播广告,播完自动下集、快捷键n键下集f键全屏、播放进度保存
// @namespace   omofuns
// @match       https://omofuns.xyz/vod/play/id/*
// @version     1.7
// @grant       none
// @require     https://cdn.jsdelivr.net/npm/[email protected]
// @require     https://cdn.jsdelivr.net/npm/[email protected]
// @resource    plyr_css https://cdn.jsdelivr.net/npm/[email protected]/dist/plyr.css
// @grant       GM_getResourceText
// @grant       GM_addStyle
// @author      star2000
// @license     AGPL-3.0-or-later
// @description 2025/7/12 16:39:37
// ==/UserScript==

GM_addStyle(GM_getResourceText("plyr_css"));

/**
 * 从URL中匹配M3U8链接
 * @param {string} url URL字符串
 * @return {string|undefined} 匹配到的M3U8链接或undefined
 */
function urlMatchM3u(url) {
  if (url) {
    const m3u8Link = url.match(/http[^?]*\.m3u8/);
    if (m3u8Link) {
      return m3u8Link[0];
    }
  }
}

/**
 * 去除M3U8文本中的插播广告
 * @param {string} text M3U8文本
 * @returns {string} 去除插播广告后的M3U8文本
 */
function pruner(text) {
  if (!text.includes("#EXT-X-DISCONTINUITY")) {
    return text;
  }

  const lines = text.split("\n");
  const pruned_lines = [];
  let is_start = false;
  let is_ad = false;
  for (let i = 0; i < lines.length; i++) {
    let l = lines[i];
    if (is_start) {
      if (l == "#EXT-X-ENDLIST") {
        is_ad = false;
      }
      if (l == "#EXT-X-DISCONTINUITY") {
        is_ad = !is_ad;
        continue;
      }
      if (is_ad) {
        continue;
      }
    } else if (l.startsWith("#EXTINF")) {
      is_start = true;
    }
    pruned_lines.push(l);
  }
  return pruned_lines.join("\n");
}

function next() {
  const nextButton = Array.from(document.querySelectorAll("a")).find(
    (a) => a.text == "下集"
  );
  if (nextButton) {
    nextButton.click();
  }
}

(function () {
  "use strict";

  // 监听按键
  document.addEventListener("keydown", function (event) {
    if (event.key === "n") {
      next();
    }
  });

  // 监听M3U8请求
  XMLHttpRequest.prototype.open = new Proxy(XMLHttpRequest.prototype.open, {
    apply: async (target, thisArg, args) => {
      if (urlMatchM3u(args[1]))
        thisArg.addEventListener("readystatechange", function () {
          if (thisArg.readyState !== 4) {
            return;
          }
          const type = thisArg.responseType;
          if (type !== "" && type !== "text") {
            return;
          }
          const textin = thisArg.responseText;
          const textout = pruner(textin);
          if (textout === textin) {
            return;
          }
          Reflect.defineProperty(thisArg, "response", { value: textout });
          Reflect.defineProperty(thisArg, "responseText", { value: textout });
        });
      return Reflect.apply(target, thisArg, args);
    },
  });

  const p = document.getElementsByClassName("player-box-main")[0];
  if (!p) return;
  const ifs = document.getElementsByTagName("iframe");
  let i;
  for (const k in ifs) {
    if (Object.prototype.hasOwnProperty.call(ifs, k)) {
      if (ifs[k].src.includes("url=")) {
        i = ifs[k];
        break;
      }
    }
  }
  if (!i) return;

  const video = document.createElement("video");
  video.style.aspectRatio = "16 / 9";
  video.style.width = "100%";
  video.controls = true;
  video.autofocus = true;
  video.addEventListener("ended", function () {
    next();
  });

  // 播放进度保存
  const progressKey = `${vod_name} ${vod_part}`;
  video.addEventListener("timeupdate", function () {
    if (video.currentTime) {
      localStorage.setItem(progressKey, video.currentTime);
    }
  });
  // 播放进度恢复
  video.addEventListener("loadedmetadata", function () {
    const t = localStorage.getItem(progressKey);
    if (t) {
      video.currentTime = parseFloat(t);
      video.play();
    }
  });

  const m3u8Url = urlMatchM3u(i.src);
  if (m3u8Url) {
    video.src = m3u8Url;
    const hls = new Hls();
    hls.on(Hls.Events.MANIFEST_PARSED, function () {
      video.play();
    });
    hls.on(Hls.Events.ERROR, function (event, data) {
      if (data.fatal) {
        switch (data.type) {
          case Hls.ErrorTypes.MEDIA_ERROR:
          case Hls.ErrorTypes.NETWORK_ERROR:
            console.log("error");
            hls.recoverMediaError();
            break;
          default:
            hls.destroy();
        }
      }
    });
    hls.loadSource(video.src);
    hls.attachMedia(video);
  } else {
    video.src = i.src.split("url=")[1];
  }
  while (p.firstChild) {
    p.removeChild(p.firstChild);
  }
  p.appendChild(video);

  new Plyr(video, {
    keyboard: {
      focused: false,
      global: true,
    },
    controls: [
      "play-large",
      "rewind",
      "play",
      "fast-forward",
      "progress",
      "current-time",
      "duration",
      "mute",
      "volume",
      "settings",
      "pip",
      "fullscreen",
    ],
  });
})();