<template>
  <div>
    <v-container>
      <v-row align="center" justify="space-around" >
          <v-btn v-if="!visibility" v-on:click="start">Start</v-btn>
          <v-btn v-if="visibility" v-on:click="stop">Stop</v-btn>
      </v-row>
    </v-container>
    <v-container>
      <v-row align="center" justify="center" style="height: 33vh;">
          <video v-show="visibility && !message" id="video" muted class="h-100" preload="none"></video>
          <div v-if="message">{{this.message}}</div>
      </v-row>
    </v-container>      
    <v-container>
      <v-row align="center" justify="center">
          {{ this.rtspinfo?.url }}
      </v-row>
    </v-container>
  </div>
</template>

<script>
import axios from "axios";

export default {
  data() {
    return {
      visibility: true,
      ws: null,
      message: null,
      rtspinfo: null,
      videoCanvas: null,
      format: null
    };
  },
  mounted() {
    const videoCanvas = document.createElement("canvas");
    this.videoCanvas = videoCanvas.getContext("2d");
    const video = document.getElementById("video");
    video.srcObject = videoCanvas.captureStream();
    video.play();
    this.videoCanvas.clearRect(0,0,videoCanvas.width,videoCanvas.height);
    axios.get("/api/rtspinfo").then(
      (response) => this.rtspinfo = response.data
    );
    axios.get("/api/isCapturing").then(
      (response) => this.visibility = response.data
    );
  },
  created() {
    let wsurl = new URL("./ws", location.href);
    wsurl.protocol = wsurl.protocol.replace("http","ws");
    this.connectWebSocket(wsurl.href);
  },
  destroyed() {
    console.log("Closing WebSocket");
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  },
  methods: {
    start() {
      axios.get("/api/start").then( () => this.visibility = true);
    },
    stop() {
      axios.get("/api/stop").then( () => this.visibility = false);
    },
    async onMessage(message) {
        if (typeof message.data === 'string') {
          this.format = JSON.parse(message.data);

        } else if (message.data instanceof ArrayBuffer) {
          const bytes = new Uint8Array(message.data);
          if ( (bytes.length > 1) && (bytes[0] === 255) && (bytes[1] === 216)) {
              // JPEG
              let binaryStr = "";
              for (let i = 0; i < bytes.length; i++) {
                binaryStr += String.fromCharCode(bytes[i]);
              }
              this.message = null;
              const img = new Image();
              img.src = "data:image/jpeg;base64," + btoa(binaryStr);
              await new Promise(r => img.onload=r);
              this.videoCanvas.canvas.width = img.width;
              this.videoCanvas.canvas.height = img.height;
              this.videoCanvas.drawImage(img, 0, 0);
              if (this.ws.decoder) {
                this.ws.decoder.close();
              }
              this.ws.decoder = null;
          } else if ( (bytes.length > 3) && (bytes[0] === 0) && (bytes[1] === 0) && (bytes[2] === 0) && (bytes[3] === 1)) {
              // H264
              if (!this.ws.decoder) {
                this.ws.decoder = new VideoDecoder({
                  output: (frame) => {
                    this.videoCanvas.canvas.width = frame.displayWidth;
                    this.videoCanvas.canvas.height = frame.displayHeight;
                    this.videoCanvas.drawImage(frame, 0, 0);
                    frame.close();
                  },
                  error: (e) => console.log(e.message),
                });
              }

              const naluType = bytes[4] & 0x1F;
              if (this.ws.decoder.state !== "configured" && naluType === 7) {
                  let codec = 'avc1.';
                  for (let i = 0; i < 3; i++) {
                      codec += ('00' + bytes[5+i].toString(16)).slice(-2);
                  }
                  const config = {codec};
                  const support = await VideoDecoder.isConfigSupported(config);
                  if (support.supported) {
                    this.ws.decoder.configure(config);
                  } else {
                    this.message = `${codec} is not supported`;                    
                  }
              } 
              if (this.ws.decoder.state === "configured") {
                  this.message = null;
                  const chunk = new EncodedVideoChunk({
                      timestamp: new Date().getTime(),
                      type: (naluType === 7) || (naluType === 5) ? "key" : "delta",
                      data: bytes,
                  });
                  this.ws.decoder.decode(chunk);
              }
          } else if (this.format) {
              try {
                const frame = new VideoFrame(bytes, {
                    format: this.format.format,
                    timestamp: performance.now(),
                    codedWidth: this.format.width,
                    codedHeight: this.format.height,
                });            
                this.videoCanvas.canvas.width = frame.displayWidth;
                this.videoCanvas.canvas.height = frame.displayHeight;
                this.videoCanvas.drawImage(frame, 0, 0);
                frame.close();            
                this.message = null;
              } catch (e) {
                this.message = `${this.format.format} is not supported`;  
              }

          } else {
            this.message = 'format not supported';  
          }
        }      
    },
    connectWebSocket(wsurl) {
        console.log(`Connecting WebSocket to ${wsurl}`);
        this.ws = new WebSocket(wsurl);
        this.ws.binaryType = 'arraybuffer';
        this.ws.onmessage = this.onMessage;
        this.ws.onclose = () => {
          console.log(`WebSocket closed, reconnecting to ${wsurl}...`);
          setTimeout(() => this.connectWebSocket(wsurl), 1000);
        };
    }
  }
};
</script>

<style>
</style>
