我只想好好观影

为了想看就看

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name        我只想好好观影
// @namespace   liuser.betterworld.love
// @match       https://movie.douban.com/subject/*
// @match       https://m.douban.com/movie/*
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @grant       GM_registerMenuCommand
// @grant       GM_setValue
// @grant       GM_getValue
// @connect     *
// @run-at      document-end
// @require     https://cdnjs.cloudflare.com/ajax/libs/artplayer/5.1.0/artplayer.min.js
// @require     https://unpkg.com/artplayer-plugin-control@2.0.0/dist/artplayer-plugin-control.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.12/hls.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.9/vue.min.js
// @version     3.9.6
// @author      liuser, collaborated with ray
// @description 为了想看就看
// @license MIT
// ==/UserScript==
//vue production https://cdn.jsdelivr.net/npm/vue@2.7.14
//vue dev https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js
//按钮样式

// @note 按钮样式
GM_addStyle(
  `
        .liu-btn{
          cursor:pointer;
          font-size:1rem;
          padding: 0.6rem 1.2rem;
          border: 1px solid transparent;
          border-radius: 3px;
          max-height:50px;
        }

        .play-btn {
          border-radius: 8px;
          cursor: pointer;
          font-weight: bolder;
          background-color:#e8f5e9;
        }
        .play-btn:hover {
          background-color:#c8e6c9;
        }
        .play-btn:active{
          background-color: #81c784;
        }

        .source-selector{
          background-color: #141414;
          color: #99a2aa;
          padding:0.6rem 0.8rem;
          margin:0.5rem 0.875rem;
          border-radius:4px;
        }
        .source-selector:hover{
          background-color: #1f1f1f;
        }

        .series-selector{
          background-color: #141414;
          border-radius:3px;
          color: #99a2aa;
          font-size:16px;
          padding: 12px 16px;

        }
        .series-selector:hover{
          background-color: #153a1d;
        }

        .playing{
          border:2px solid #007011;
        }

        .selected{
          border:2px solid #007011;
        }


        .liu-closePlayer{
          border-radius:3px;
          background-color: #141414;
            float:right;
          color: #99a2aa;
          width:2rem;
          height:2rem;
          line-height:2rem;
          padding:0;
          margin:0.5rem 1rem;
        }
        .liu-closePlayer:hover{
          background-color:#1f1f1f;
          color:white;
        }

        .love-support{
          color:#99a2aa;
          background-color:tranparent;
          margin-right:32px;
        }


        a:visited{
          color:#99a2aa;
        }
        a:hover{
          font-weight:bold;
          color:#A8DB39;
          background:none;
        }


        `
);

//@note 剧集选择器布局
GM_addStyle(
  `
        .series-contianer{
          display:grid;
          grid-template-columns: repeat(4,1fr);
          grid-auto-rows:50px;
          grid-column-gap:16px;
          grid-row-gap:16px;
          margin-top:16px;
          height:524px;
          overflow-y:scroll;
        }
        .series-contianer::-webkit-scrollbar {
          display: none;
          }
        @media screen and (max-width: 1025px) {
          .series-contianer{
            display:grid;
            grid-template-columns: repeat(6,1fr);
          }

        }


        `
);

//布局@note 整体布局
GM_addStyle(
  `
        :root{
          font-size:16px;
          font-family: BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif !important;
        }
        :root::-webkit-scrollbar {
          display: none;
        }

        .TalionNav{
            z-index:10;
        }
        .speed-slow{
            color:#9e9e9e;
        }
        .speed-fast{
            color:#4aa150;
        }



        .mannul{
          margin:16px 0px 64px 14px;
          font-size:16px;
          display:flex;
          flex-wrap:wrap;
        }
        .authoralert{
          font-size:16px;
          margin-left:14px;
          color:#F76965;
        }



        .liu-playContainer{
            width:100%;
            height:100%;
            background-color:#1c2022;
            position:fixed;
            top:0;
            z-index:11;
            overflow:auto;
        }
        .liu-playContainer::-webkit-scrollbar {
          display: none;
        }



        .video-selector{
            display:flex;
            flex-wrap:wrap;
            margin-top:1rem;
        }

        .liu-selector:hover{
            color:#aed0ee;
            background-color:none;
        }

        .liu-selector{
            color:black;
            cursor:pointer;
            padding:3px;
            margin:5px;
            border-radius:2px;
        }

        .liu-rapidPlay{
            color: #007722;
        }

        .liu-light{
            background-color:#7bed9f;
        }

        .artplayer-app{
          height:600px;
        }


        .playSpace{
            display: grid;
        /* 	height:400px; */
          margin:1rem;
            grid-template-columns: 2fr 1fr;
            grid-row-gap:0px;
            grid-column-gap:1rem;
          margin-top:2rem;
          clear: both;
        }



        @media screen and (max-width: 1025px) {
            .playSpace{
                display: grid;
        /* 		height:600px; */
                grid-template-rows: 1fr 0.5fr;
                grid-template-columns:1fr;
                grid-row-gap:10px;
                grid-column-gap:0px;
            }
        }


        .seletor-title{
          height:60px;
          line-height:3rem;
          background-color: #141414;
          color:#fafafa;
          font-size:1.25rem;
          padding: 0 1rem;
        }
        `
);

// 上传额外源的信息
const sourceupload = () => {
  let sourceAdded = prompt(
    "请输入自定义源,名称与链接用|隔开,每一项用英文逗号隔开,例子:XX资源|https://xx.com/,YY资源|https://yy.com/"
  );
  GM_setValue("sourceAdded", sourceAdded);
};
// 注册菜单按钮
GM_registerMenuCommand("自定义源", sourceupload);

(function () {
  const _debug = 0; //@note debug
  const searchSource = [
    //@note 内置搜索源
    {
      name: "红牛资源",
      searchUrl: "https://www.hongniuzy2.com/api.php/provide/vod/from/hnm3u8/",
    },
    {
      name: "暴风资源",
      searchUrl: "https://bfzyapi.com/api.php/provide/vod/",
    },
    // {
    //   name: "快帆资源",
    //   searchUrl: "https://api.kuaifan.tv/api.php/provide/vod/",
    // }, 失效

    {
      name: "非凡资源",
      searchUrl: "http://cj.ffzyapi.com/api.php/provide/vod/",
    },
    {
      name: "量子资源",
      searchUrl: "https://cj.lziapi.com/api.php/provide/vod/",
    },
    {
      name: "ikun资源",
      searchUrl:
        "https://ikunzyapi.com/api.php/provide/vod/from/ikm3u8/at/json/",
    },
    {
      name: "光速资源",
      searchUrl: "https://api.guangsuapi.com/api.php/provide/vod/from/gsm3u8/",
    },
    {
      name: "高清资源",
      searchUrl: "https://api.1080zyku.com/inc/apijson.php/",
    },
    {
      name: "天空资源",
      searchUrl:
        "https://m3u8.tiankongapi.com/api.php/provide/vod/from/tkm3u8/",
    }, //有防火墙,垃圾
    {
      name: "闪电资源",
      searchUrl: "https://sdzyapi.com/api.php/provide/vod/",
    }, //不太好,格式经常有错
    {
      name: "索尼资源",
      searchUrl: "https://suoniapi.com/api.php/provide/vod/",
    },
    {
      name: "飞速资源",
      searchUrl: "https://www.feisuzyapi.com/api.php/provide/vod/",
    }, //经常作妖或者没有资源
    {
      name: "卧龙资源",
      searchUrl: "https://collect.wolongzyw.com/api.php/provide/vod/",
    }, //非常恶心的广告
    // { "name": "8090资源", "searchUrl": "https://api.yparse.com/api/json/m3u8/" },垃圾 可能有墙
    {
      name: "百度云资源",
      searchUrl: "https://api.apibdzy.com/api.php/provide/vod/",
    },
    // { "name": "酷点资源", "searchUrl": "https://kudian10.com/api.php/provide/vod/" },
    {
      name: "淘片资源",
      searchUrl: "https://taopianapi.com/cjapi/mc/vod/json/m3u8.html",
    },
    // { "name": "ck资源", "searchUrl": "https://ckzy.me/api.php/provide/vod/" },
    {
      name: "快播资源",
      searchUrl: "https://caiji.kczyapi.com/api.php/provide/vod/",
    },
    {
      name: "乐视资源",
      searchUrl: "https://leshiapi.com/api.php/provide/vod/at/json/",
    },
    {
      name: "优质资源",
      searchUrl: "https://api.1080zyku.com/inc/apijson.php",
    },
    {
      name: "丫丫资源",
      searchUrl: "https://cj.yayazy.net/api.php/provide/vod/",
    },
    {
      name: "金鹰资源",
      searchUrl: "https://jyzyapi.com/provide/vod/from/jinyingm3u8/at/json",
    },
    {
      name: "快播资源",
      searchUrl: "https://caiji.kczyapi.com/api.php/provide/vod/",
    },
    // { "name": "海外看资源", "searchUrl": "http://api.haiwaikan.com/v1/vod/" }, // 说是屏蔽了所有中国的IP,所以如果你有外国的ip可能比较好
    // { "name": "68资源", "searchUrl": "https://caiji.68zyapi.com/api.php/provide/vod/" },
    // {"name":"鱼乐资源","searchUrl":"https://api.yulecj.com/api.php/provide/vod/"},//速度太慢
    {
      name: "无尽资源",
      searchUrl: "https://api.wujinapi.me/api.php/provide/vod/",
    }, //资源少
  ];
  const { query: $, queryAll: $$, isMobile } = Artplayer.utils; //工具函数

  const tip = (message) => alert(message);
  // 判断是否为 Edge 浏览器
  const isEdge = /Edge\/\d+/.test(navigator.userAgent);

  // 判断是否为 Chrome 浏览器
  const isChrome = /Chrome\/\d+/.test(navigator.userAgent) && !isEdge;

  // 判断是否为 Safari 浏览器
  const isSafari = /Safari\/\d+/.test(navigator.userAgent) && !isChrome;

  //--------------------------全局方法
  //获取豆瓣影片名称
  const videoName = isMobile
    ? $(".sub-title").innerText
    : document.title.slice(0, -5).replace(" ", "");

  // debug方法
  const log = (function () {
    if (_debug) return console.log.bind(console);
    return function () {};
  })();

  const htmlToElement = function (html) {
    //将html字符串转为element
    const template = document.createElement("template");
    template.innerHTML = html.trim();
    return template.content.firstChild;
  };

  //速度为0不一定是无法播放,可能是源的防火墙阻止了测速,也可以试试。
  const handleResponse = function (response) {
    // log("正在处理搜索的结果");
    if (!response) {
      // log("返回结果错误,response is undefined");
      return { r: false };
    }
    if (response.list.length == 0) {
      // log("没有搜索到结果");
      return { r: false };
    }
    let video,
      found = false;

    for (let item of response.list) {
      // 对比名称、发行年、演员,只要有一个一样就算成功
      let nameEqual = item.vod_name == videoName;
      let yearEqual = getVideoYear(item.vod_year);
      let actorContain = videoActor(item.vod_actor.split(",")[0]);

      if (yearEqual === true || actorContain === true || nameEqual === true) {
        video = item;
        found = true;
        // log(`资源匹配成功`);
        break;
      }
    }
    if (found == false) {
      return { r: false };
    }
    let vod_name = video.vod_name;
    let playList = video.vod_play_url
      .split("$$$")
      .filter((str) => str.includes("m3u8"));
    if (playList.length == 0) {
      throw new Error("没有m3u8资源, 无法测速, 无法播放");
      return { r: false };
    }
    playList = playList[0].split("#");
    playList = playList.map((str) => {
      let index = str.indexOf("$");
      return {
        name: str.slice(0, index),
        url: str.slice(index + 1),
        speed: -1,
      };
    });
    return { r: true, content: playList, vod_name: vod_name };
  };

  //播放按钮
  class PlayBtn {
    constructor() {
      const e = htmlToElement(
        `<button class="liu-btn play-btn">一键播放</button>`
      );
      $(isMobile ? ".sub-original-title" : "h1").appendChild(e);

      e.onclick = function () {
        initVue();
      };
    }
  }

  const playM3u8 = function (video, url, art) {
    if (Hls.isSupported()) {
      if (art.hls) art.hls.destroy();
      const hls = new Hls();
      hls.loadSource(url);
      hls.attachMedia(video);
      art.hls = hls;
      art.on("destroy", () => hls.destroy());
    } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
      video.src = url;
    } else {
      art.notice.show = "Unsupported playback format: m3u8";
    }
  };
  //获取电影的年份
  const getVideoYear = function (outYear) {
    const e = $(isMobile ? ".sub-original-title" : ".year");
    if (!e) {
      // log("获取年份失败,请检查!");
      return 0;
    }
    return e.innerText.includes(outYear);
  };

  //对比电影演员
  const videoActor = function (outActor) {
    const e = $(isMobile ? ".bd" : ".actor");
    if (!e) {
      // log("获取演员失败,请检查!");
      return 0;
    }
    //log(`${outActor}:匹配结果${e.innerText.includes(outActor)}`)
    return e.innerText.includes(outActor);
  };

  //下载
  const get = function (detail) {
    //@note get
    log("正在请求:");
    log(detail);
    return new Promise((resolve, reject) => {
      let timer = setTimeout(() => {
        resolve({ r: false });
      }, 3000);

      let defaultConfig = {
        method: "GET",
        timeout: 3000,
        onload: (r) => {
          clearTimeout(timer);
          resolve({ r: true, content: r.response });
        },
        onerror: () => {
          log("get请求error " + detail.url);
          resolve({ r: false });
        },
        onabort: () => {
          log("get请求abort " + detail.url);
          resolve({ r: false });
        },
        ontimeout: () => {
          log("get请求timeout " + detail.url);
          resolve({ r: false });
        },
      };
      let config = Object.assign(defaultConfig, detail);
      GM_xmlhttpRequest(config);
    });
  };

  //下载m3u8的内容,返回片段列表
  const downloadtsList = async function (url) {
    let domain = url.split("/")[0];
    let baseUrl = url.split("/")[2];
    let result = await get({
      url: encodeURI(url),
    });

    if (!result.r) {
      return { r: false };
    }
    let downloadContent = result.content;

    if (!downloadContent.includes("#EXTM3U")) {
      log("无法获取m3u8内容,请求网址为:" + url);
      log("下载的内容为");
      log(downloadContent);
      return { r: false };
    }
    let tsList = [];
    if (downloadContent.includes(".m3u8")) {
      //如果还是m3u8地址
      let lines = downloadContent.split("\n");
      for (let item of lines) {
        if (/^[#\s]/.test(item)) continue; //跳过注释和空白行
        if (item == "") continue;
        if (/^\//.test(item)) {
          //如果是相对链接的话
          let result = await downloadtsList(domain + "//" + baseUrl + item);
          if (!result.r) {
            return { r: false };
          }
          tsList = result.content;
        } else if (/^https?:\/\//i.test(item)) {
          //如果是绝对链接的话
          let result = await downloadtsList(item);
          if (!result.r) {
            return { r: false };
          }
          tsList = result.content;
        } else {
          //那就只剩下替代链接的情况了
          log("m3u8替代情况");
          log(item);
          let contents = url.split("/");
          contents[contents.length - 1] = item;
          log(contents);
          url = contents.join("/");
          let result = await downloadtsList(url);
          if (!result.r) {
            return { r: false };
          }
          tsList = result.content;
        }
      }
      return { r: true, content: tsList };
    }
    if (downloadContent.includes(".ts")) {
      //如果是ts地址
      let lines = downloadContent.split("\n");
      for (let item of lines) {
        if (/^[#\s]/.test(item)) continue; //跳过注释和空白行
        if (item == "") continue;
        if (/^https?:\/\//i.test(item)) {
          //如果是http直链
          tsList.push(item);
        } else if (/^\//.test(item)) {
          //如果是相对链接
          tsList.push(domain + "//" + baseUrl + item);
        } else {
          //如果不是相对链接就把index.m3u8替换掉就行
          let contents = url.split("/");
          contents[contents.length - 1] = item;
          url = contents.join("/");
          tsList.push(url);
        }
      }
      log(`测试列表为:`);
      log(tsList);
      return { r: true, content: tsList };
    }

    log("未知状况");
    log(downloadContent);
    return { r: false };
  };

  //app的整体结构,作为vue的渲染模板
  //@note vueAppTemplate
  const vueAppTemplate = `
        <div  id="app">
          <div class="liu-playContainer" v-show="ok">
            <button class="liu-closePlayer liu-btn" @click="closePlayer">
              <svg class="icon" width="50%" height="50%" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4012"><path d="M587.19 506.246l397.116-397.263a52.029 52.029 0 0 0 0-73.143l-2.194-2.194a51.98 51.98 0 0 0-73.143 0l-397.068 397.8-397.068-397.8a51.98 51.98 0 0 0-73.143 0l-2.146 2.194a51.054 51.054 0 0 0 0 73.143l397.069 397.263L39.544 903.461a52.029 52.029 0 0 0 0 73.142l2.146 2.195a51.98 51.98 0 0 0 73.143 0L511.9 581.583l397.068 397.215a51.98 51.98 0 0 0 73.143 0l2.194-2.146a52.029 52.029 0 0 0 0-73.143L587.19 506.246z" p-id="4013" data-spm-anchor-id="a313x.search_index.0.i1.10e63a81F8aVUU" class="selected" fill="#ffffff"></path></svg>
            </button>

            <!-- 播放模块 -->
            <div class="playSpace">
              <!-- 视频容器 -->
              <div class="artplayer-app"></div>
              <!-- 选集模块 -->
              <div class="series">
                <div class="seletor-title">{{vod_name}}选集</div>
                <div class="series-contianer">
                  <button
                    class="series-selector liu-btn"
                    :class="{'playing':index==playingIndex}"
                    style="color: #a3a3a3"
                    v-for="(item,index) in playingList"
                    :key="index"
                    @click="playListSelect(index)"
                  >
                    {{item.name.slice(0,4)}}
                    <!-- 固定宽度不够长 -->
                  </button>
                </div>
              </div>
            </div>

            <!-- 源选择模块 -->
            <div class="sourceButtonList">
              <button
                class="source-selector liu-btn"
                v-for="(item,index) in sources"
                :key="index"
                :class="{'selected':index==selectedSource,'speed-fast':item.speed>1}"
                @click="sourceSelect(index)"
              >
                {{item.name}} {{item.speed}} m/s
              </button>
            </div>
            <!-- 一些说明 -->
            <p class="authoralert">请不要相信视频中的广告!</p>
            <div class="mannul">

              <a
                class="love-support"
                style="text-decoration: #447006 wavy underline;"
                href="https://pay.babelgo.cn/"
                target="_blank"
                >☕打赏可联系作者定制功能
                </a>
              <a
                class="love-support"
                href="https://t.me/wzxhhgy"
                target="_blank"
                >电报群</a
              >
              <a
                class="love-support"
                href="https://greasyfork.org/zh-CN/scripts/459540-%E6%88%91%E5%8F%AA%E6%83%B3%E5%A5%BD%E5%A5%BD%E8%A7%82%E5%BD%B1/feedback"
                target="_blank"
                >👉反馈</a
              >
            </div>
          </div>
        </div>

          `;
  //构建vue的实例
  function initVue() {
    //@note initVue
    const e = htmlToElement(vueAppTemplate);
    document.body.appendChild(e);
    let vueInstance = new Vue({
      el: "#app",
      data: {
        //artplayer实例
        art: {},
        //标明是否搜到影片
        ok: false,
        //资源中的片名
        vod_name: "",
        //搜索源
        searchSource: searchSource,
        //标记正在下载的资源,好debug
        sourceTesting: "",
        //所有搜索到的资源 [{name:"..资源",playList:[{name:"第一集",url:""}]}]
        sources: [],
        //标记选择的源
        selectedSource: 0,
        //正在播放的选集总表
        playingList: [],
        //正在播放哪一集
        playingIndex: 0,
        //咖啡地址
        coffeeUrl: "https://pay.babelgo.cn/",
        //telegram地址
        telegramUrl: "https://t.me/wzxhhgy",
        //greasyfork反馈地址
        feedbackUrl:
          "https://greasyfork.org/zh-CN/scripts/459540-%E6%88%91%E5%8F%AA%E6%83%B3%E5%A5%BD%E5%A5%BD%E8%A7%82%E5%BD%B1/feedback",
      },
      methods: {
        //@note merge自定义源
        mergeSource() {
          let sourceAdded = GM_getValue("sourceAdded", ""); //获取用户添加的额外搜索源
          log(sourceAdded);
          //@ 兼容sourceAdded为null的情况
          if (sourceAdded == "") return;
          sourceAdded.split(",").forEach((item) => {
            if (item === "") return;
            let name_url = item.split("|");
            this.searchSource.push({
              name: name_url[0],
              searchUrl: name_url[1],
            });
          });
        },
        //测试速度
        async testSpeed() {
          //@note testSpeed
          //this.sources //所有搜索到的资源 [{name:"..资源",playList:[{name:"第一集",url:""}]}]
          await Promise.all(
            this.sources.map(async (source, index) => {
              try {
                let name = source.name;
                let url = source.playList[this.playingIndex].url;
                log("开始测速:" + url);
                let result = await downloadtsList(url);
                // 如果无法获取index.m3u8 就直接无法测速
                if (!result.r) {
                  this.sources[index].speed = 0;
                  this.$forceUpdate();
                  return;
                }
                let tsList = result.content;
                log("downloadList");
                log(tsList);
                //随机选择8个切片下载
                if (tsList.length > 10) {
                  tsList = tsList.slice(0, 10);
                } else {
                  tsList = tsList.slice(0, tsList.length - 1);
                }
                let downloadSize = 0;
                let startTime = Date.now();
                log("将要测试的列表为:");
                log(tsList);
                for (let item of tsList) {
                  let result = await get({
                    url: encodeURI(item),
                    responseType: "arraybuffer",
                  });
                  if (!result.r) {
                    downloadSize += 0;
                  } else {
                    let response = result.content;
                    downloadSize += response.byteLength
                      ? response.byteLength / 1024 / 1024
                      : 0;
                  }
                }
                let endTime = Date.now();
                let duration = (endTime - startTime) / 1000;
                let speed =
                  downloadSize / duration ? downloadSize / duration : 0;
                this.sources[index].speed = Number(speed.toFixed(2));
                this.$forceUpdate();
                log(`${name}的速度为${speed}mb/s`);
              } catch (e) {
                log(e);
                this.sources[index].speed = 0;
                this.$forceUpdate();
              }
            })
          );
        },
        //选择源的按钮行为
        sourceSelect(index) {
          let ct = this.art.currentTime;
          this.selectedSource = index;
          this.playingList = this.sources[index].playList;
          this.switchUrl(this.playingList[this.playingIndex].url);
          this.art.once("video:canplay", () => {
            this.art.seek = ct;
          });
          log("vod名称切换");
          this.vod_name = this.sources[index].vod_name;
          log(this.vod_name)

          this.testSpeed().then(() => {
            log("测速完成!");
          });
        },
        //选择剧集的按钮行为
        playListSelect(index) {
          this.playingIndex = index;
          //打印playlist
          log(this.playingList);
          this.switchUrl(this.playingList[this.playingIndex].url);
        },
        // 初始化Art播放器
        initArt(url) {
          //@note initArt
          this.art = new Artplayer({
            container: ".artplayer-app",
            url: url,
            pip: true,
            fullscreen: true,
            fullscreenWeb: true,
            autoMini: true,
            screenshot: true,
            hotkey: true,
            airplay: true,
            playbackRate: true,
            setting: true,
            miniProgressBar: true,
            theme: "#00981a",
            moreVideoAttr: {
              crossOrigin: "anonymous",
            },
            controls: [
              {
                name: "resolution",
                html: "分辨率",
                position: "right",
              },
            ],
            type: "m3u8",
            customType: {
              m3u8: playM3u8,
            },
            //   plugins: [artplayerPluginControl()],
          });
          this.art.on("video:loadedmetadata", () => {
            this.art.controls.resolution.innerText =
              this.art.video.videoHeight + "P";
          });
          log("初始化art实例完成,art:");
          log(this.art);
        },
        //切换播放器的播放url
        switchUrl(url) {
          this.art.switchUrl(url);
          //兼容safari
          if (this.art.video.src != url) {
            this.art.video.src = url;
          }
        },
        //关闭页面
        closePlayer() {
          $("#app").remove();
        },

        async search(url) {
          let splitVideoName = "";
          if (videoName.length >= 3) {
            splitVideoName = videoName.slice(0, 3);
          } else {
            splitVideoName = videoName;
          }
          let result = await get({
            url: encodeURI(`${url}?ac=detail&wd=${splitVideoName}`),
            responseType: "json",
            overrideMimeType: "application/json",
          });
          if (!result.r) {
            return { r: false, content: "搜索时网络出现异常" };
          }
          let response = result.content;
          log(response);
          return { r: true, content: response };
        },
        //处理搜索到的结果:从返回结果中找到对应片子
      },
      async created() {
        //@note created
        //初始化时开始搜索所有资源, 初始化sources 数组

        // this.searchSource.forEach();
        this.mergeSource(); //merge 用户添加的地址
        await Promise.all(
          this.searchSource.map(async (item) => {
            //标记当前正在处理的资源
            this.sourceTesting = item.name;
            // search里自带解析函数,所以只要通过就说明搜索到了
            let result = await this.search(item.searchUrl);
            if (!result.r) {
              return { r: false, content: "搜索出现异常" };
            }
            let response = result.content;
            result = handleResponse(response);
            if (result.r) {
              log(`${item.name} 搜到了`);
              let playList = result.content;
              this.sources.push({
                name: item.name,
                playList,
                vod_name: result.vod_name,
              });
              if (this.ok == false) {
                this.ok = true;
                this.$forceUpdate();
                this.playingList = playList;
                this.initArt(this.playingList[0].url);
              }
            } else {
              log(`${item.name}没找到`);
            }
          })
        );

        // 此时还不能检测是否已经搜索完毕,因为前面发的是async
        // 因为Safari的xmlhttp方法在失败时不会返回reject导致Promise一直等待,
        // 导致后面的代码无法执行,所以需要手动实现timeout机制
        // 结束以后ok还为false说明没搜索
        if (this.ok == true) {
          //开始测速
          if (isSafari) {
            tip("很可惜,Safari浏览器存在严重Bug,所以测速功能工作不正常。");
          }
          log("开始测速");
          await this.testSpeed();
        } else {
          tip(
            "未搜索到资源,可能是豆瓣的电影名称和资源站的名称不一致,请反馈电影名称。"
          );
          window.open(
            "https://greasyfork.org/zh-CN/scripts/459540-%E6%88%91%E5%8F%AA%E6%83%B3%E5%A5%BD%E5%A5%BD%E8%A7%82%E5%BD%B1/feedback"
          );
        }
      },
    });
  }

  new PlayBtn();
})();