class ResilientVideoJsPlayer {
  constructor(videoElement, srcUrl) {
    this.video = videoElement;
    this.srcUrl = srcUrl;

    this.player = null;
    this._initPlayer();
    this._attachCoreEvents();
    this._attachTechEvents();
    this.setSource(srcUrl);
  }

  log(...args) {
    console.log("[VideoJS]", ...args);
  }

  _initPlayer() {
    if (!this.player) {
      this.player = videojs(this.video, {
        autoplay: false,
        controls: true,
        preload: "auto",
        liveui: true
      });
      this.log("player initialized");
    }
  }

  setSource(url) {
    this.srcUrl = url;

    this.log("setSource:", url);

    this.player.src({
      src: url,
      type: "application/x-mpegURL"
    });

    this.player.ready(() => {
      this.log("player ready -> play()");
      this.player.play().catch(e => this.log("play() error:", e));
    });
  }

  // -----------------------------
  // Video.js level events
  // -----------------------------
  _attachCoreEvents() {
    const p = this.player;

    const events = [
      "loadstart",
      "loadedmetadata",
      "loadeddata",
      "canplay",
      "canplaythrough",
      "play",
      "playing",
      "pause",
      "waiting",
      "stalled",
      "seeking",
      "seeked",
      "ended",
      "error",
      "dispose"
    ];

    events.forEach(ev => {
      p.on(ev, () => {
        this.log(`event:${ev}`, this._getState());
      });
    });

    // error details
    p.on("error", () => {
      const err = p.error();
      this.log("VIDEOJS ERROR:", err);
    });
  }

  // -----------------------------
  // HTML5 tech(video element) events
  // -----------------------------
  _attachTechEvents() {
    const v = this.video;

    const events = [
      "timeupdate",
      "ratechange",
      "progress",
      "durationchange",
      "volumechange"
    ];

    events.forEach(ev => {
      v.addEventListener(ev, () => {
        if (ev === "timeupdate") return; // clip the time update event
        this.log(`tech:${ev}`, this._getState());
      });
    });
  }

  // -----------------------------
  // get the status snapshots
  // -----------------------------
  _getState() {
    const v = this.video;

    return {
      currentTime: v.currentTime?.toFixed(3),
      readyState: v.readyState,
      paused: v.paused,
      playbackRate: v.playbackRate,
      buffered: this._getBuffered(v),
      networkState: v.networkState
    };
  }

  _getBuffered(v) {
    try {
      if (!v.buffered || v.buffered.length === 0) return 0;
      return (v.buffered.end(v.buffered.length - 1) - v.currentTime).toFixed(3);
    } catch {
      return 0;
    }
  }
}

function VideoJsStart() {
  const urlNew = document.getElementById('m3u8URLSave').value;
  const video = document.getElementById('oVideo');

  if (!video._player) {
    video._player = new ResilientVideoJsPlayer(video, urlNew);
  } else {
    video._player.setSource(urlNew);
  }
}

