<template>
  <div class="page-container">
    <div id="video-container" style="position: relative" class="page-container">
      <div v-if="shouldShowSections" class="instruction-tips above-video">
        <div
          class="instruction-animation"
          :style="{
            width: '33.3%',
            'font-size': 16,
            padding: '0 10px',
            'margin-left': reverseSection * 33 + '%',
          }"
          data-intro="停留的时间：每一次停留时长可能不一样。"
        >
          请将脸部移入此区域，停留<span style="color: blue">{{
            currentStep.stay
          }}</span
          >秒，并保持<span style="color: blue">{{
            shouldDetectExpression ? (currentStep.happy ? "笑容" : "严肃") : ""
          }}</span>
        </div>
      </div>
      <div class="instruction-tips above-video">
        <div
          class="instruction-animation"
          :style="{
            width: '100%',
            'font-size': '18px',
            'text-align': 'center',
          }"
        >
          <div v-if="currentStep.action === 'FACE_COMPARE'">
            请调整脸部与摄像头的距离
          </div>
          <div v-else>
            保持<span style="color: blue">{{
              shouldDetectExpression
                ? currentStep.happy
                  ? "笑容"
                  : "严肃"
                : ""
            }}</span>
            <Progress hide-info :percent="stepProgress" />
          </div>
        </div>
      </div>

      <div
        v-if="isDetecting"
        class="instruction-total above-video"
        style="z-index: 3"
      >
        <div class="total-text" data-intro="请在规定的时间内完成。">
          {{ instructions.total }}
        </div>
      </div>
      <div v-if="shouldShowSections" class="seperators above-video">
        <div class="line"></div>
        <div class="line"></div>
      </div>
      <!-- <div v-if="!behaving" class="blocks above-video">
        <div class="block-index">1</div>
        <div class="block-index">2</div>
        <div class="block-index">3</div>
      </div> -->
      <div v-if="shouldShowSections" class="blocks above-video">
        <div
          v-for="item in [3, 2, 1]"
          :key="item"
          :class="[
            'block-index-size',
            currentStep.section !== item && 'block-index-blur',
          ]"
        ></div>
      </div>
      <div
        v-if="shouldShowSections"
        :class="[
          'above-video',
          'instruction-face',
          'instruction-animation',
          behaving && 'instruction-face-animation-state',
        ]"
        :style="{
          width: '33.3%',
          'margin-left': reverseSection * 33 + '%',
        }"
        data-intro="停留的位置：请将脸部停留在头像所处的列。检测成功，头像会停止抖动。"
      >
        <!-- <el-progress
          type="circle"
          :stroke-width="12"
          :show-text="false"
          :percentage="stepProgress"
          style="margin-top: -13px;"
          class="above-video"
        ></el-progress> -->
        <!-- <div
          style="margin: 0 auto; border-radius: 50%; margin-top: -50px; width: 200px; height: 200px; border: 10px solid black;"
        ></div> -->
      </div>
      <div
        v-if="behaving"
        :class="[
          'above-video',
          'instruction-face',
          'instruction-animation',
          behaving && 'instruction-face-animation-state',
        ]"
        :style="{
          width: '33.3%',
          'margin-left': 1 * 33 + '%',
        }"
      >
        <!-- <el-progress
          type="circle"
          :stroke-width="12"
          :show-text="false"
          :percentage="stepProgress"
          style="margin-top: -13px;"
          class="above-video"
        ></el-progress> -->
      </div>
      <video
        id="inputVideo"
        class="detect-video"
        style="transform: scaleX(-1);"
        autoplay
        muted
        @loadedmetadata="onPlay"
      ></video>
      <canvas id="overlay" class="above-video" />
    </div>

    <div style="position: absolute; top: 0; left: 0; display:none;">
      <img
        id="base-photo"
        src="/student_base_photo/0/6/1560392244118.jpg"
        style="width: 150px;"
      />
    </div>
  </div>
</template>

<script>
import * as faceapi from "face-api.js";
import MD5 from "js-md5";
// import introJs from "intro.js";
import throttle from "lodash-es/throttle";

// models path
const modelsPath = "/models/20190620/";
window.faceapi = faceapi;

// let withBoxes = true;

const os = (function() {
  const ua = navigator.userAgent.toLowerCase();
  return {
    isWin2K: /windows nt 5.0/.test(ua),
    isXP: /windows nt 5.1/.test(ua),
    isVista: /windows nt 6.0/.test(ua),
    isWin7: /windows nt 6.1/.test(ua),
    isWin8: /windows nt 6.2/.test(ua),
    isWin81: /windows nt 6.3/.test(ua),
    isWin10: /windows nt 10.0/.test(ua),
  };
})();

// tiny_face_detector options

function getFaceDetectorOptions() {
  let inputSize = 160;
  if (os.isWin7) {
    inputSize = 256; // 在win7上无bug，速度快，效果较好
  } else if (os.isWin10) {
    inputSize = 320; // 在win10上，效果较好
  }

  window.____hideMe =
    window.____hideMe ||
    new faceapi.TinyFaceDetectorOptions({
      inputSize, // 这行是解决Box.constructor - expected box to be IBoundingBox | IRect, instead have 问题的关键
      scoreThreshold: 0.5,
    });
  return window.____hideMe;
  // return new faceapi.SsdMobilenetv1Options({ minConfidence: 0.8 });
  // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
}

export default {
  name: "FaceMotion",
  data() {
    return {
      isDetecting: false,
      shoudAdjustDistance: true,
      introStarted: false,
      asked: false,
      instructions: {
        total: 60,
        steps: [
          { section: 2, stay: 3, happy: true, finished: false },
          { section: 3, stay: 5, happy: true, finished: false },
          { section: 2, stay: 4, happy: true, finished: false },
        ],
      },
      behaving: false,
      behavingStartDate: null,
      behavingTimestampe: null,
      shouldDetectExpression: null,
      pauseDetecting: false,
      compareResult: null,
      finalResult: {
        examRecordDataId: 0,
        faceBiopsyItemId: 0,
        verifySteps: [
          {
            action: "FACE_COMPARE",
            errorMsg: "string",
            resourceType: "PIC",
            resourceUrl: "string",
            result: true,
            resultJson: "string",
            stay: 0,
            stepId: 0,
          },
        ],
      },
    };
  },
  computed: {
    shouldShowSections() {
      if (this.currentStep.section === 0) return false;
      if (this.introStarted) return true;
      if (this.isDetecting && this.shoudAdjustDistance) return false;
      return this.isDetecting;
    },
    currentStep() {
      return this.instructions.steps.find(v => !v.finished) || {};
    },
    corFinalResult() {
      const idx = this.finalResult.verifySteps.findIndex(
        v => v.stepId === this.currentStep.stepId
      );
      return this.finalResult.verifySteps[idx];
    },
    instructionsFinished() {
      return this.instructions.steps.every(v => v.finished);
    },
    reverseSection() {
      return [3, 2, 1][this.currentStep.section - 1] - 1;
    },
    stepProgress() {
      if (this.instructionsFinished) return 0;
      if (!this.behaving) return 100;
      let progress =
        100 -
        (100 * (this.behavingTimestampe - this.behavingStartDate)) /
          (this.currentStep.stay * 1000);
      // console.log("progress: ", progress);
      if (progress > 100) {
        progress = 100;
      } else if (progress < 0) {
        progress = 0;
      }
      return progress;
    },
  },
  watch: {
    instructionsFinished(finished) {
      if (finished) {
        clearInterval(this.remainInteval);
        this.isDetecting = false;
        // this.$message({
        //   message: "恭喜你，活体检测通过",
        //   type: "success",
        // });
        this.$Message.success({
          content: "活体检测通过",
          duration: 5,
        });
        // this.resetTest();
        this.closeMe();
      }
    },
    "instructions.total"(total) {
      if (total <= 0) {
        console.log(this.corFinalResult);
        this.corFinalResult.timeout = true;
        this.corFinalResult.result = false;
        this.corFinalResult.errorMsg = "超时！活体检测失败！";
        this.failedTest("超时！活体检测失败！");
      }
    },
  },
  async created() {
    // console.log(faceapi);
    // console.log(faceapi.nets.tinyFaceDetector);
    this.$Spin.show({});
    await this.fetchData();
    this.resetTest();

    await faceapi.nets.tinyFaceDetector.load(modelsPath);
    await faceapi.loadFaceLandmarkModel(modelsPath);
    await faceapi.nets.faceExpressionNet.load(modelsPath);
    // await faceapi.nets.ssdMobilenetv1.load(modelsPath);
    // await faceapi.nets.faceRecognitionNet.load(modelsPath);
    faceapi.tf.ENV.set("WEBGL_PACK", false);

    // faceapi.nets.mtcnn.load(modelsPath);
  },
  mounted() {
    this.run();
  },
  methods: {
    async fetchData() {
      const examRecordDataId = this.$route.params.examRecordDataId;
      // FIXME: 失败了再取？
      const faceBiopsyInfoData = await this.$http.get(
        "/api/ecs_oe_student/faceBiopsy/getFaceBiopsyInfo?examRecordDataId=" +
          examRecordDataId
      );

      const faceBiopsyInfo = faceBiopsyInfoData.data;
      console.log(faceBiopsyInfo);
      this.faceBiopsyInfo = faceBiopsyInfo;
    },
    async closeMe() {
      const faceLiveResultData = await this.$http.post(
        "/api/ecs_oe_student/faceBiopsy/saveFaceBiopsyResult",
        this.finalResult
      );
      const faceLiveResult = faceLiveResultData.data;
      console.log(faceLiveResult);
      this.$emit("closeFaceMotion", faceLiveResult);
    },
    resetTest() {
      // this.isDetecting = true;
      // this.asked = false;
      this.shoudAdjustDistance = true;
      this.behavingStartDate = null;
      this.happyFailedTimes = 0;
      this.singleFaceFailedTimes = 0;
      const steps = this.faceBiopsyInfo.verifySteps
        // .filter(s => ["HAPPY", "SERIOUS"].includes(s.action))
        .map(s => {
          return {
            section: 0,
            stay: s.stay,
            happy: s.action === "HAPPY",
            finished: false,
            stepId: s.stepId,
            action: s.action,
          };
        });
      this.instructions = {
        total: 60,
        steps,
      };

      console.log(this.instructions);

      this.shouldDetectExpression = true;
      // this.shouldDoFaceRecognition = true;
      const examRecordDataId = this.$route.params.examRecordDataId;
      this.finalResult = {
        examRecordDataId,
        faceBiopsyItemId: this.faceBiopsyInfo.faceBiopsyItemId,
        verifySteps: this.faceBiopsyInfo.verifySteps,
      };
    },
    async run() {
      // load face detection and face landmark models
      // await changeFaceDetector(TINY_FACE_DETECTOR)
      // changeInputSize(224);
      // try to access users webcam and stream the images
      // to the video element
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        // video: {},
        video: {
          // width: { min: "100vw" },
          // height: { min: "100vh" },
          width: 640,
          height: 480,
          frameRate: 15,
          // resizeMode: "crop-and-scale",
        },
      });
      // console.log(
      //   "video stream settings",
      //   stream.getVideoTracks()[0].getSettings()
      // );
      // console.log(
      //   "video stream constraints",
      //   stream.getVideoTracks()[0].getConstraints()
      // );
      // console.log(
      //   "video stream capabilities",
      //   stream.getVideoTracks()[0].getCapabilities()
      // );
      const videoEl = document.getElementById("inputVideo");
      videoEl.srcObject = stream;
    },
    async intro() {
      // this.$message({
      //   type: "info",
      //   message: "开始活体检测",
      //   duration: 1500,
      // });
      this.$Message.info({ content: "开始活体检测", duration: 2 });
      // let loading;
      // loading = this.$Spin.show({});
      this.$Spin.show({});

      // this.isDetecting = true;
      this.behavingStartDate = null;
      // this.introStarted = true;
      // this.$nextTick(() => {
      //   const io = introJs()
      //     .setOptions({
      //       nextLabel: "下一步",
      //       prevLabel: "上一步",
      //       skipLabel: "跳过",
      //       doneLabel: "完成",
      //     })
      //     .start();
      //   // loading.hide();
      //   this.$Spin.hide();
      //   const realStart = () => {
      //     this.isDetecting = true;
      //     clearInterval(this.remainInteval);
      //     this.remainInteval = setInterval(() => {
      //       this.instructions.total--;
      //       if (this.instructions.total <= 0) {
      //         clearInterval(this.remainInteval);
      //       }
      //     }, 1000);
      //     this.onPlay();
      //   };

      //   io.onbeforeexit(() => {
      //     // loading = this.$Spin.show({});
      //     this.$Spin.show({});
      //   });
      //   io.onexit(() => {
      //     setTimeout(() => {
      //       realStart();
      //       this.introStarted = false;
      //       // console.log("exit intro");
      //       // loading.hide();
      //       this.$Spin.hide();
      //     }, 300);
      //   });
      // });
      const realStart = () => {
        this.isDetecting = true;
        clearInterval(this.remainInteval);
        this.remainInteval = setInterval(() => {
          this.instructions.total--;
          if (this.instructions.total <= 0) {
            clearInterval(this.remainInteval);
          }
        }, 1000);
        this.onPlay();
      };

      realStart();
    },
    async onPlay() {
      if (!this.doneCompare && this.pauseDetecting) {
        console.log({ pauseDetecting: this.pauseDetecting });
        return;
      }
      if (!this.asked) {
        this.asked = true;
        await this.increaseTestSpeed();
        // await new Promise(resolve => setTimeout(resolve, 3000));
        await this.intro();

        // this.$confirm("开始活体检测？", "确认开始")
        //   .then(async () => {
        //   })
        //   .catch(() => {
        //     this.$message({
        //       type: "info",
        //       message: "刷新可重新选择",
        //     });
        //   });
      }
      if (!this.isDetecting) return;
      const detectStartTime = performance.now();
      const videoEl = document.getElementById("inputVideo");

      this.___vWidth =
        this.___vWidth ||
        document.getElementById("video-container").clientWidth;

      const options = getFaceDetectorOptions();
      let result;

      /**
       * tiny
       * 无表情，无landmarks，60~70ms
       * 有表情，增加20~30ms
       * 有landmarks，增加20~30ms
       *
       * ssdMobilenetv1
       * 无表情，无landmarks，130ms
       * 有表情，增加30~40ms
       * 有landmarks，增加20~30ms
       *
       * mtcnn
       * 无表情，无landmarks，200ms 每次差异很大
       * 有表情，增加30~40ms
       * 有landmarks，增加20~30ms
       *
       */

      if (this.shouldDetectExpression) {
        // const canvas2 = faceapi.createCanvasFromMedia(videoEl);
        result = await faceapi
          // .detectSingleFace(videoEl, options)
          // .detectAllFaces(canvas2, options)
          .detectAllFaces(videoEl, options)
          .withFaceLandmarks()
          .withFaceExpressions();
        // if (result.length === 0) {
        //   document.body.appendChild(canvas2);
        // }
      } else {
        result = await faceapi
          // .detectSingleFace(videoEl, options)
          .detectAllFaces(videoEl, options)
          .withFaceLandmarks();
      }

      // console.log(result);
      if (result && result.length >= 2) {
        this.corFinalResult.result = false;
        this.corFinalResult.errorMsg = "检测到多张人脸！活体检测失败！";
        this.failedTest("检测到多张人脸！活体检测失败！");
      }

      if (result && result.length === 0) {
        if (!this.shoudAdjustDistance) {
          // 只有不是调整人脸距离的时候增加失败次数
          this.singleFaceFailedTimes++;
        }
        if (this.singleFaceFailedTimes >= 5) {
          this.corFinalResult.result = false;
          this.corFinalResult.errorMsg =
            "活检过程中没有检测到人脸！活体检测失败！";
          this.failedTest("活检过程中没有检测到人脸！活体检测失败！");
        }
      }

      // 人脸比对 - 开始
      {
        if (
          this.shouldDoFaceRecognition &&
          faceapi.nets.ssdMobilenetv1.params &&
          faceapi.nets.faceRecognitionNet.params
        ) {
          const personFromVideo = await faceapi
            // .detectSingleFace(videoEl, options)
            .detectSingleFace(videoEl, options)
            .withFaceLandmarks()
            .withFaceDescriptor();
          const personFromBasePhoto = await faceapi
            .detectSingleFace(document.getElementById("base-photo"))
            .withFaceLandmarks()
            .withFaceDescriptor();

          if (personFromVideo && personFromBasePhoto) {
            // create FaceMatcher with automatically assigned labels
            // from the detection results for the reference image
            const faceMatcher = new faceapi.FaceMatcher(personFromBasePhoto);
            const bestMatch = faceMatcher.findBestMatch(
              personFromVideo.descriptor
            );
            if (bestMatch.distance > 0.8) {
              console.log("%c肯定不是王章军", "color: red");
            }
            if (bestMatch.distance >= 0.4 && bestMatch.distance <= 0.8) {
              console.log("有可能是王章军");
            }
            if (bestMatch.distance < 0.4) {
              console.log("%c肯定是王章军", "color: green");
            }
            console.log(bestMatch.toString());
          }
        }
      }
      // 人脸比对 - 结束

      if (result && result[0]) {
        result = result[0];
        // Object.entries(result.expressions).forEach(([key, value]) => {
        //   if (value > 0.5) {
        //     console.log(key, value);
        //   }
        // });
        // console.log(Object.entries(result.expressions));
        // console.log(".......");
        // const canvasStartTime = performance.now();
        const canvas = document.getElementById("overlay");
        const dims = faceapi.matchDimensions(canvas, videoEl, true);
        const resizedResult = faceapi.resizeResults(result, dims);
        // const canvasEndTime = performance.now();
        // console.log(" canvas time: ", canvasEndTime - canvasStartTime);

        // console.log(resizedResult);
        // console.log(resizedResult.detection.box.top);
        // console.log(resizedResult.detection.box.left);
        let box;
        if (this.shouldDetectExpression || resizedResult.detection) {
          // 在检测表情时有detection属性，没有box属性。没有检测landmarks
          box = resizedResult.detection.box;
        } else {
          box = resizedResult.box;
        }
        // console.log(box.area);
        if (box.area > 60000 || box.area < 20000) {
          const message = box.area > 60000 ? "请远离摄像头" : "请靠近摄像头";
          this.tipHandler =
            this.tipHandler ||
            throttle(message => {
              // this.$message({
              //   type: "warning",
              //   message,
              //   duration: 1000,
              //   offset: 300,
              // });
              this.$Message.warning({ content: message, duration: 1 });
            }, 1500);
          this.tipHandler(message);
          if (this.shoudAdjustDistance) {
            setTimeout(() => this.onPlay(), 300);
            return;
          }
        } else {
          this.shoudAdjustDistance = false;
          if (this.currentStep.action === "FACE_COMPARE") {
            console.log("该做同步人脸比对了");
            // 同步人脸比对
            try {
              if (this.pauseDetecting || this.doneCompare) return;
              this.pauseDetecting = true;
              const cs = await this.snap();
              console.log(cs);
              if (!cs) return;
              this.compareResult = cs;
              // 后台的计算需要通过resultJson来判断: isPass isStranger existsSystemError
              this.finalResult.verifySteps[0].result = this.compareResult.isPass;
              this.finalResult.verifySteps[0].resourceType = "PIC";
              this.finalResult.verifySteps[0].resourceUrl = this.compareResult.fileUrl;
              this.finalResult.verifySteps[0].resultJson = this.compareResult.faceCompareResult;
              this.finalResult.verifySteps[0].errorMsg = this.compareResult.errorMsg;
              this.pauseDetecting = false;
              this.doneCompare = true;
              if (this.compareResult.isPass) {
                console.log("人脸同步比对成功");
                if (!this.instructionsFinished && this.currentStep)
                  this.currentStep.finished = true;
              } else {
                this.failedTest("人脸同步比对失败");
                return;
              }
            } catch (error) {
              console.log(error);
            } finally {
              // this.pauseDetecting = false;
              this.videoStartPlay();
            }
          }
        }
        // 区域左边的一半
        const centerPoint = box.left + (box.right - box.left) / 2;
        if (
          (centerPoint >
            this.___vWidth * ((this.currentStep.section - 1) / 3) &&
            centerPoint < this.___vWidth * (this.currentStep.section / 3)) ||
          this.currentStep.section === 0
        ) {
          if (this.behavingStartDate === null) {
            // 到指定区块后才开始检测表情
            if (this.shouldDetectExpression) {
              if (
                (result.expressions.happy < 0.5 && this.currentStep.happy) ||
                (result.expressions.neutral < 0.5 && !this.currentStep.happy)
              ) {
                // this.$message({
                //   type: "warning",
                //   message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                //   duration: 1000,
                //   offset: 300,
                // });
                this.$Message.warning({
                  content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                  duration: 1,
                });
                setTimeout(() => this.onPlay(), 1000);
                return;
              }
            }
            this.behavingStartDate = new Date();
          }
          this.behaving = true;
          this.behavingTimestampe = Date.now();
        } else {
          this.behaving = false;
          this.behavingStartDate = null;

          if (!this.moveFaceMessage) {
            // this.moveFaceMessage = this.$message({
            //   type: "info",
            //   message: "请将您的脸部移向笑脸所在的区块",
            //   duration: 3000,
            //   offset: 300,
            // });
            this.moveFaceMessage = this.$Message.info({
              content: "请将您的脸部移向笑脸所在的区块",
              duration: 1,
            });
            setTimeout(() => {
              this.moveFaceMessage = null;
            }, 3000);
          }
        }
        const detectEndTime = performance.now();
        console.log("single detect time: ", detectEndTime - detectStartTime);
        if (this.shouldDetectExpression && this.behavingStartDate) {
          // 到指定区块后才开始检测表情
          if (result.expressions.happy < 0.5 && this.currentStep.happy) {
            this.happyFailedTimes++;
            if (this.happyFailedTimes % 2) {
              if (Date.now() - (this.showExpresionTipDate || 0) > 1500) {
                this.showExpresionTipDate = Date.now();
                // this.$message({
                //   type: "warning",
                //   message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                //   duration: 1000,
                //   offset: 300,
                // });
                this.$Message.warning({
                  content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                  duration: 1,
                });
              }
            }
          }
          // if(result.expressions.happy >= 0.5 && this.currentStep.happy) {
          //   this.happyFailedTimes = 0; // 恢复的话，容易降低恶意用户攻击难度
          // }
          if (result.expressions.neutral < 0.5 && !this.currentStep.happy) {
            this.happyFailedTimes++;
            if (this.happyFailedTimes % 2) {
              if (Date.now() - (this.showExpresionTipDate || 0) > 1500) {
                this.showExpresionTipDate = Date.now();
                // this.$message({
                //   type: "warning",
                //   message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                //   duration: 1000,
                //   offset: 300,
                // });
                this.$Message.warning({
                  content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
                  duration: 1,
                });
              }
            }
          }
          if (this.happyFailedTimes >= 6) {
            this.corFinalResult.result = false;
            this.corFinalResult.errorMsg = "指定表情失败！活体检测失败！";
            this.failedTest("指定表情失败！活体检测失败！");
          }
        }

        const stayMoreForProgress = 500; // wait for progress reach 0/100
        if (
          this.behaving &&
          Date.now() - this.behavingStartDate - stayMoreForProgress >
            this.currentStep.stay * 1000
        ) {
          console.log("通过section" + this.currentStep.section);
          this.behaving = false;
          this.happyFailedTimes = 0;
          this.behavingStartDate = null;
          if (!this.instructionsFinished && this.currentStep) {
            this.corFinalResult.result = true;
            this.currentStep.finished = true;
          }
        }
        // console.log(resizedResult.alignedRect.relativeBox.y);
        // if (true) {
        //   faceapi.draw.drawDetections(canvas, resizedResult);
        // }
        // faceapi.draw.drawFaceLandmarks(canvas, resizedResult);
      }
      setTimeout(() => this.onPlay(), 300);
    },
    failedTest(msg) {
      clearInterval(this.remainInteval);
      this.isDetecting = false;
      if (!this.instructionsFinished) {
        // this.$message({
        //   message: msg || "活体检测失败",
        //   type: "error",
        // });
        this.$Message.error({ content: msg || "活体检测失败", duration: 5 });
      }
      // this.resetTest();
      this.closeMe();
    },
    async increaseTestSpeed() {
      if (!this.__inThisMethodOnce) {
        this.__inThisMethodOnce = true;
      } else {
        return;
      }
      const videoEl = document.getElementById("inputVideo");
      const options = getFaceDetectorOptions();
      console.log("increaseTestSpeed ---");
      await new Promise(resolve => {
        const interval = setInterval(() => {
          if (
            videoEl.readyState === 4 &&
            faceapi.nets.tinyFaceDetector.params &&
            faceapi.nets.faceExpressionNet.params
            // !faceapi.nets.ssdMobilenetv1.params
            // !faceapi.nets.mtcnn.params
          ) {
            resolve();
            clearInterval(interval);
          }
        }, 300);
      });
      console.log(videoEl.readyState, faceapi.nets.tinyFaceDetector.params);
      console.log("increaseTestSpeed --- doing faceapi");
      const result = await faceapi
        .detectSingleFace(videoEl, options)
        .withFaceLandmarks()
        .withFaceExpressions();
      console.log("increaseTestSpeed --- result:", result);
      if (!result) {
        console.log("increaseTestSpeed --- end failed");
      } else {
        console.log("increaseTestSpeed --- end successfully");
      }
      this.$Spin.hide();
    },
    videoStartPlay() {
      const video = document.getElementById("inputVideo");
      video && video.play();
    },
    async snap() {
      if (this.disableSnap) {
        return;
      }
      try {
        this.disableSnap = true;
        const captureBlob = await this.getSnapShot();
        if (captureBlob.size < 10 * 1024) {
          this.$Message.error({
            content: "抓拍照片太小！",
            duration: 15,
            closable: true,
          });
          // 经查以前记录，不完整图片均为8192大小。此处设置小于10KB的图片为未抓拍成功
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "抓拍照片较小",
            captureBlob.size,
          ]);
          throw "抓拍照片较小";
        }
        this.videoStartPlay();
        const [captureFilePath, signIdentifier] = await this.uploadToServer(
          captureBlob
        );
        return this.faceCompareSync(captureFilePath, signIdentifier);
      } catch (error) {
        console.log("同步照片比对流程失败");
        throw error;
      } finally {
        this.videoStartPlay();
        // this.disableSnap = false;
      }
    },
    async getSnapShot() {
      return new Promise((resolve, reject) => {
        const video = document.getElementById("inputVideo");
        if (video.readyState !== 4 || !video.srcObject.active) {
          this.$Message.error({
            content: "摄像头没有正常启用",
            duration: 5,
            closable: true,
          });
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "摄像头状态",
            "摄像头没有正常启用-退出" +
              (this.lastSnapTime ? "(非初次抓拍)" : ""),
          ]);
          reject("摄像头没有正常启用");
          this.logout(
            "?LogoutReason=" +
              "摄像头没有正常启用-退出" +
              (this.lastSnapTime ? "(非初次抓拍)" : "")
          );
          return;
        }
        video.pause();
        var canvas = document.createElement("canvas");
        canvas.width = 220;
        canvas.height = 165;

        var context = canvas.getContext("2d");
        context.drawImage(video, 0, 0, 220, 165);

        canvas.toBlob(resolve, "image/png", 0.95);
      });
    },
    async uploadToServer(captureBlob) {
      async function blobToArray(blob) {
        return new Promise(resolve => {
          var reader = new FileReader();
          reader.addEventListener("loadend", function() {
            // reader.result contains the contents of blob as a typed array
            resolve(reader.result);
          });
          reader.readAsArrayBuffer(blob);
        });
      }
      //保存抓拍照片到服务器
      let resultUrl, signIdentifier;
      try {
        const buffer = await blobToArray(captureBlob);

        // console.log(buffer);
        // var view1 = new Uint8Array(buffer);
        // console.log(buffer[0], buffer[1], buffer[429721]);
        const fileMd5 = MD5(buffer);
        console.log(fileMd5);

        const params = new URLSearchParams();
        params.append("fileSuffix", "png");
        params.append("fileMd5", fileMd5);
        const res = await this.$http.get(
          "/api/ecs_oe_student/examControl/getCapturePhotoUpYunSign?" + params
        );

        // console.log(res);

        // let myHeaders = new Headers();
        // for (let [k, v] of Object.entries(res.data.headers)) {
        //   // console.log(k, v);
        //   if (k.includes("tion") || k.includes("Date") || k.includes("MD5")) {
        //     if (k === "Date") k = "x-date";
        //     myHeaders.append(k, v);
        //   }
        let myFormData = new FormData();
        for (let [k, v] of Object.entries(res.data.formParams)) {
          myFormData.append(k, v);
        }
        myFormData.append("file", captureBlob);

        try {
          const res2 = await fetch(res.data.formUrl, {
            method: "POST",
            body: myFormData,
          });
          if (!res2.ok) {
            throw res2.status;
          }
        } catch (error) {
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "抓拍照片保存失败--upyun",
            error,
          ]);
          throw error;
        }

        // console.log(response);
        resultUrl = res.data.accessUrl;
        signIdentifier = res.data.signIdentifier;
        // this.serverLog("debug/S-005001", "抓拍照片保存成功：");
        window._hmt.push(["_trackEvent", "摄像头框", "抓拍照片保存成功"]);
      } catch (e) {
        console.log(e);
        // this.serverLog("debug/S-006001", "抓拍照片保存失败");
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "保存抓拍照片到服务器失败！",
        ]);
        this.$Message.error({
          content: "抓拍照片保存失败！",
          duration: 15,
          closable: true,
        });
        throw "抓拍照片保存失败！";
      }
      return [resultUrl, signIdentifier];
    },
    async faceCompareSync(captureFilePath, signIdentifier) {
      try {
        const res = await this.$http.post(
          "/api/ecs_oe_student_face/examCaptureQueue/compareFaceSync?signIdentifier=" +
            signIdentifier +
            "&fileUrl=" +
            encodeURIComponent(captureFilePath)
        );

        // // TODO: 识别成功、失败的通知或跳转
        // this.$emit("on-recognize-result", {
        //   error: null,
        //   pass: res.data.isPass,
        //   stranger: res.data.isStranger,
        // });
        return res.data;
      } catch (e) {
        console.log(e);
        // this.$Message.error(e.message);
        throw "同步照片比较失败！";
      }
    },
  },
};
</script>

<style scoped>
.page-container {
  /* margin-left: 20px; */
  margin: 0 auto;
  width: 640px;
  height: 480px;
  overflow: hidden;
}
.above-video {
  z-index: 2;
}
.instruction-animation {
  transition: margin-left 2s ease-in-out 0.5s;
}
.instruction-tips {
  position: absolute;
  top: 0;
  left: 0;
  text-align: center;
  width: 100%;
  background-color: rgba(255, 255, 255, 0.6);
  padding: 20px 0;
}
.instruction-total {
  position: absolute;
  top: 50px;
  left: 0;
  text-align: center;
  width: 100%;
  background-color: rgba(255, 255, 255, 0);
}
.total-text {
  background-color: rgba(255, 255, 255, 0.6);
  width: 80px;
  height: 80px;
  line-height: 80px;
  font-size: 40px;
  border-radius: 50%;
  border: 3px solid gold;
  margin: 20px auto 0 auto;
}
.seperators {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.1);

  display: flex;
  justify-content: space-evenly;
}

.seperators .line {
  width: 5px;
  height: 100%;
  background-color: red;
}

.blocks {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.1);

  display: flex;
  justify-content: space-around;
  align-items: center;
}

.blocks .block-index {
  margin-top: 150px;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background-color: rgba(255, 0, 0, 0.7);
  color: yellow;
  font-size: 50px;
  line-height: 100px;
  text-align: center;
}

.blocks .block-index-size {
  margin-top: 175px;
  width: 100%;
  height: 100%;
  transition: all 1s ease-out;
}

.blocks .block-index-blur {
  background-color: rgba(100, 100, 100, 0.8);
}

.instruction-face {
  position: absolute;
  top: 30%;
  left: 0;
  width: 33%;
  height: 100px;
  background-size: contain;
  background-repeat: no-repeat;
  background-position-x: center;
  text-align: center;

  /* background-color: rgba(255, 255, 255, 0.6); */
  /* background-image: url(./smile-icon.png); */

  animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both infinite;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}

.instruction-face-animation-state {
  animation-iteration-count: 1;
}
#overlay,
.overlay {
  position: absolute;
  top: 0;
  left: 0;
}

/* .detect-video {
  width: 100vw;
  height: 100vh;
} */

@keyframes shake {
  10%,
  90% {
    transform: translate3d(-1px, 0, 0);
  }

  20%,
  80% {
    transform: translate3d(2px, 0, 0);
  }

  30%,
  50%,
  70% {
    transform: translate3d(-4px, 0, 0);
  }

  40%,
  60% {
    transform: translate3d(4px, 0, 0);
  }
}
</style>

<style>
.el-message__content {
  font-size: 24px !important;
}
</style>
