wlzx 发表于 2024-3-4 10:14:52

LVDS0接口频率

RV1126LVDS0主时钟CLK0支持37.125MHZ\54MHZ\74.25MHZ时钟信号吗?
LVDS0_CLKP和LVDS0_CLKN支持297、594M信号传输吗?

0) {                  str = d % 60 + units + str;                }                d = Math.floor(d / 60)            }            return str;      } catch {            return &quot;N/A&quot;      }    }    function downloadEnabled() {      try {            if (window.VideoTogetherDownload == 'disabled') {                return false;            }            const type = VideoTogetherStorage.UserscriptType            return parseInt(window.VideoTogetherStorage.LoaddingVersion) >= 1694758378                && (type == &quot;Chrome&quot; || type == &quot;Safari&quot; || type == &quot;Firefox&quot;)                && !isDownloadBlackListDomain()      } catch {            return false;      }    }    function isM3U8(textContent) {      return textContent.trim().startsWith('#EXTM3U');    }    function isMasterM3u8(textContent) {      return textContent.includes('#EXT-X-STREAM-INF:');    }    function getFirstMediaM3U8(m3u8Content, m3u8Url) {      if (!isMasterM3u8(m3u8Content)) {            return null;      }      const lines = m3u8Content.split('\n');      for (const line of lines) {            const trimmedLine = line.trim();            if (trimmedLine && !trimmedLine.startsWith('#') && trimmedLine != &quot;&quot;) {                return new URL(trimmedLine, m3u8Url);            }      }      return null;    }    function startDownload(_vtArgM3u8Url, _vtArgM3u8Content, _vtArgM3u8Urls, _vtArgTitle, _vtArgPageUrl) {      /*//*/(async function () {    function extractExtXKeyUrls(m3u8Content, baseUrl) {      const uris = [];      const lines = m3u8Content.split('\n');      for (let i = 0; i{            const id = setTimeout(() => {                reader.cancel();                rej(new Error('Stream read timed out'));            }, timeout);      });      return Promise.race([            reader.read(),            timer      ]);    }    function generateUUID() {      if (crypto.randomUUID != undefined) {            return crypto.randomUUID();      }      return ( + -1e3 + -4e3 + -8e3 + -1e11).replace(//g, c =>            (c ^ crypto.getRandomValues(new Uint8Array(1)) & 15 >> c / 4).toString(16)      );    }    window.updateM3u8Status = async function updateM3u8Status(m3u8Url, status) {      // 0 downloading1 completed 2 deleting      let m3u8mini = await readFromIndexedDB('m3u8s-mini', m3u8Url);      m3u8mini.status = status      await saveToIndexedDB('m3u8s-mini', m3u8Url, m3u8mini);    }    async function saveM3u8(m3u8Url, m3u8Content) {      await saveToIndexedDB('m3u8s', m3u8Url,            {                data: m3u8Content,                title: vtArgTitle,                pageUrl: vtArgPageUrl,                m3u8Url: m3u8Url,                m3u8Id: m3u8Id,                status: 0            }      )    }    async function blobToDataUrl(blob) {      return new Promise((resolve, reject) => {            const reader = new FileReader();            reader.onload = function (event) {                resolve(event.target.result);            };            reader.onerror = function (event) {                reject(new Error(&quot;Failed to read blob&quot;));            };            reader.readAsDataURL(blob);      });    }    async function saveBlob(table, url, blob) {      return new Promise(async (res, rej) => {            try {                const dataUrl = await blobToDataUrl(blob);                await saveToIndexedDB(table, url, {                  data: dataUrl,                  m3u8Url: downloadM3u8Url,                  m3u8Id: m3u8Id,                })                res();            } catch (e) {                rej(e);            }      })    }    window.regexMatchKeys = function regexMatchKeys(table, regex) {      const queryId = generateUUID()      return new Promise((res, rej) => {            window.postMessage({                source: &quot;VideoTogether&quot;,                type: 2005,                data: {                  table: table,                  regex: regex,                  id: queryId                }            }, '*')            regexCallback = (data) => {                try {                  res(data)                } catch { rej() }            }      })    }    saveToIndexedDBThreads = 1;    window.saveToIndexedDB = async function saveToIndexedDB(table, key, data) {      while (saveToIndexedDBThreadssetTimeout(r, 100));      }      saveToIndexedDBThreads--;      const queryId = generateUUID();      return new Promise((res, rej) => {            data.saveTime = Date.now()            window.postMessage({                source: &quot;VideoTogether&quot;,                type: 2001,                data: {                  table: table,                  key: key,                  data: data,                  id: queryId,                }            }, '*')            data = null;            saveCallback = (error) => {                saveToIndexedDBThreads++;                if (error === 0) {                  res(0)                } else {                  rej(error)                }            }      })    }    window.iosDeleteByPrefix = async function iosDeleteByPrefix(prefix) {      const queryId = generateUUID();      return new Promise((res, rej) => {            window.postMessage({                source: &quot;VideoTogether&quot;,                type: 3010,                data: {                  prefix: prefix,                  id: queryId,                }            }, '*')            deleteByPrefix = (error) => {                if (error === 0) {                  res(0)                } else {                  rej(error)                }            }      })    }    let readCallback = {}    let regexCallback = {}    let deleteCallback = {}    let saveCallback = {}    let deleteByPrefix = {}    window.addEventListener('message', async e => {      if (e.data.source == &quot;VideoTogether&quot;) {            switch (e.data.type) {                case 2003: {                  saveCallback(e.data.data.error)                  saveCallback = undefined                  break;                }                case 2004: {                  readCallback(e.data.data.data)                  readCallback = undefined;                  break;                }                case 2006: {                  regexCallback(e.data.data.data)                  regexCallback = undefined;                  break;                }                case 2008: {                  deleteCallback(e.data.data.error);                  deleteCallback = undefined;                  break;                }                case 3011: {                  deleteByPrefix(e.data.data.error);                  deleteByPrefix = undefined;                  break;                }                case 2010: {                  console.log(e.data.data.data);                  break;                }            }      }    })    window.requestStorageEstimate = function requestStorageEstimate() {      window.postMessage({            source: &quot;VideoTogether&quot;,            type: 2009,            data: {}      }, '*')    }    window.deleteFromIndexedDB = function deleteFromIndexedDB(table, key) {      const queryId = generateUUID()      window.postMessage({            source: &quot;VideoTogether&quot;,            type: 2007,            data: {                id: queryId,                table: table,                key: key,            }      }, '*')      return new Promise((res, rej) => {            deleteCallback = (error) => {                if (error === 0) {                  res(true);                } else {                  rej(error);                }            }      })    }    window.readFromIndexedDB = function readFromIndexedDB(table, key) {      const queryId = generateUUID();      window.postMessage({            source: &quot;VideoTogether&quot;,            type: 2002,            data: {                table: table,                key: key,                id: queryId,            }      }, '*')      return new Promise((res, rej) => {            readCallback = (data) => {                try {                  res(data);                } catch {                  rej()                }            }      })    }    if (window.videoTogetherExtension === undefined) {      return;    }    if (window.location.hostname == 'local.2gether.video') {      return;    }    let vtArgM3u8Url = undefined;    let vtArgM3u8Content = undefined;    let vtArgM3u8Urls = undefined;    let vtArgTitle = undefined;    let vtArgPageUrl = undefined;    try {      vtArgM3u8Url = _vtArgM3u8Url;      vtArgM3u8Content = _vtArgM3u8Content;      vtArgM3u8Urls = _vtArgM3u8Urls;      vtArgTitle = _vtArgTitle;      vtArgPageUrl = _vtArgPageUrl;    } catch {      return;    }    const m3u8Id = generateUUID()    const m3u8IdHead = `-m3u8Id-${m3u8Id}-end-`    const downloadM3u8Url = vtArgM3u8Url;    const numThreads = 10;    let lastTotalBytes = 0;    let totalBytes = 0;    let failedUrls = []    let urls = vtArgM3u8Urls    let successCount = 0;    videoTogetherExtension.downloadPercentage = 0;    const m3u8Key = m3u8IdHead + downloadM3u8Url    if (downloadM3u8Url === undefined) {      return;    }    await saveM3u8(m3u8Key, vtArgM3u8Content)    const otherUrl = extractExtXKeyUrls(vtArgM3u8Content, downloadM3u8Url);    const totalCount = urls.length + otherUrl.length;    console.log(otherUrl);    await downloadInParallel('future', otherUrl, numThreads);    setInterval(function () {      videoTogetherExtension.downloadSpeedMb = (totalBytes - lastTotalBytes) / 1024 / 1024;      lastTotalBytes = totalBytes;    }, 1000);    await downloadInParallel('videos', urls, numThreads);    await updateM3u8Status(m3u8Key, 1)    async function fetchWithSpeedTracking(url) {      const controller = new AbortController();      const timer = setTimeout(() => {            controller.abort();      }, 20000);      const response = await fetch(url, { signal: controller.signal });      clearTimeout(timer)      if (!response.body) {            throw new Error(&quot;ReadableStream not yet supported in this browser.&quot;);      }      const contentType = response.headers.get(&quot;Content-Type&quot;) || &quot;application/octet-stream&quot;;      const reader = response.body.getReader();      let chunks = [];      async function readStream() {            const { done, value } = await timeoutAsyncRead(reader, 60000);            if (done) {                return;            }            if (value) {                chunks.push(value);                totalBytes += value.length;            }            // Continue reading the stream            return await readStream();      }      await readStream();      const blob = new Blob(chunks, { type: contentType });      chunks = null;      return blob;    }    async function downloadWorker(table, urls, index, step, total) {      if (index >= total) {            return;      }      const url = urls;      try {            let blob = await fetchWithSpeedTracking(url);            await saveBlob(table, m3u8IdHead + url, blob);            blob = null;            successCount++;            videoTogetherExtension.downloadPercentage = Math.floor((successCount / totalCount) * 100)            console.log('download ts:', table, index, 'of', total);      } catch (e) {            await new Promise(r => setTimeout(r, 2000));            failedUrls.push(url);            console.error(e);      }      // Pick up the next work item      await downloadWorker(table, urls, index + step, step, total);    }    async function downloadInParallel(table, urls, numThreads) {      const total = urls.length;      // Start numThreads download workers      const promises = Array.from({ length: numThreads }, (_, i) => {            return downloadWorker(table, urls, i, numThreads, total);      });      await Promise.all(promises);      if (failedUrls.length != 0) {            urls = failedUrls;            failedUrls = [];            await downloadInParallel(table, urls, numThreads);      }    }})()//*/    }    function isLimited() {      while (lastRunQueue.length > 0 && lastRunQueuetimeLimitation) {            console.error(&quot;limited&quot;)            return true;      }      lastRunQueue.push(Date.now() / 1000);      return false;    }    function getVideoTogetherStorage(key, defaultVal) {      try {            if (window.VideoTogetherStorage == undefined) {                return defaultVal            } else {                if (window.VideoTogetherStorage == undefined) {                  return defaultVal                } else {                  return window.VideoTogetherStorage;                }            }      } catch { return defaultVal }    }    function getEnableTextMessage() {      return getVideoTogetherStorage('EnableTextMessage', true);    }    function getEnableMiniBar() {      return getVideoTogetherStorage('EnableMiniBar', true);    }    function skipIntroLen() {      try {            let len = parseInt(window.VideoTogetherStorage.SkipIntroLength);            if (window.VideoTogetherStorage.SkipIntro && !isNaN(len)) {                return len;            }      } catch { }      return 0;    }    function isEmpty(s) {      try {            return s.length == 0;      } catch {            return true;      }    }    function emptyStrIfUdf(s) {      return s == undefined ? &quot;&quot; : s;    }    let isDownloadBlackListDomainCache = undefined;    function isDownloadBlackListDomain() {      if (window.location.protocol != 'http:' && window.location.protocol != 'https:') {            return true;      }      const domains = [            'iqiyi.com', 'qq.com', 'youku.com',            'bilibili.com', 'baidu.com', 'quark.cn',            'aliyundrive.com', &quot;115.com&quot;, &quot;acfun.cn&quot;, &quot;youtube.com&quot;,      ];      if (isDownloadBlackListDomainCache == undefined) {            const hostname = window.location.hostname;            isDownloadBlackListDomainCache = domains.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));      }      return isDownloadBlackListDomainCache;    }    let isEasyShareBlackListDomainCache = undefined;    function isEasyShareBlackListDomain() {      if (window.location.protocol != 'https:') {            return true;      }      const domains = [            'iqiyi.com', 'qq.com', 'youku.com',            'bilibili.com', 'baidu.com', 'quark.cn',            'aliyundrive.com', &quot;115.com&quot;, &quot;pornhub.com&quot;, &quot;acfun.cn&quot;, &quot;youtube.com&quot;,            // --            &quot;missav.com&quot;, &quot;nivod4.tv&quot;      ];      if (isEasyShareBlackListDomainCache == undefined) {            const hostname = window.location.hostname;            isEasyShareBlackListDomainCache = domains.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));      }      return isEasyShareBlackListDomainCache;    }    function isEasyShareEnabled() {      if (inDownload) {            return false;      }      try {            if (isWeb()) {                return false;            }            if (isEasyShareBlackListDomain()) {                return false;            }            return window.VideoTogetherEasyShare != 'disabled' && window.VideoTogetherStorage.EasyShare != false;      } catch {            return false;      }    }    function isEasyShareMember() {      try {            return window.VideoTogetherEasyShareMemberSite == true;      } catch {            return false;      }    }    function useMobileStyle(videoDom) {      let isMobile = false;      if (window.location.href.startsWith('https://m.bilibili.com/')) {            isMobile = true;      }      if (!isMobile) {            return;      }      document.body.childNodes.forEach(e => {            try {                if (e != videoDom && e.style && e.id != 'VideoTogetherWrapper') {                  e.style.display = 'none'                }            } catch { }      });      videoDom.setAttribute('controls', true);      videoDom.style.width = videoDom.style.height = &quot;100%&quot;;      videoDom.style.maxWidth = videoDom.style.maxHeight = &quot;100%&quot;;      videoDom.style.display = 'block';      if (videoDom.parentElement != document.body) {            document.body.appendChild(videoDom);      }    }    const mediaUrlsCache = {}    function extractMediaUrls(m3u8Content, m3u8Url) {      if (mediaUrlsCache == undefined) {            let lines = m3u8Content.split(&quot;\n&quot;);            let mediaUrls = [];            let base = undefined;            try {                base = new URL(m3u8Url);            } catch { };            for (let i = 0; i`%${c.charCodeAt(0).toString(16).toUpperCase()}`      ).replace(/%20/g, '+');    }    function fixedDecodeURIComponent(str) {      return decodeURIComponent(str.replace(/\+/g, ' '));    }    function isWeb() {      try {            let type = window.VideoTogetherStorage.UserscriptType;            return type == 'website' || type == 'website_debug';      } catch {            return false;      }    }    /**   * @returns {Element}   */    function select(query) {      let e = window.videoTogetherFlyPannel.wrapper.querySelector(query);      return e;    }    function hide(e) {      if (e) e.style.display = 'none';    }    function show(e) {      if (e) e.style.display = null;    }    function isVideoLoadded(video) {      try {            if (isNaN(video.readyState)) {                return true;            }            return video.readyState >= 3;      } catch {            return true;      }    }    function isRoomProtected() {      try {            return window.VideoTogetherStorage == undefined || window.VideoTogetherStorage.PasswordProtectedRoom != false;      } catch {            return true;      }    }    function changeBackground(url) {      let e = select('.vt-modal-body');      if (e) {            if (url == null || url == &quot;&quot;) {                e.style.backgroundImage = 'none';            } else if (e.style.backgroundImage != `url(&quot;${url}&quot;)`) {                e.style.backgroundImage = `url(&quot;${url}&quot;)`            }      }    }    function changeMemberCount(c) {      extension.ctxMemberCount = c;      select('#memberCount').innerHTML = String.fromCodePoint(&quot;0x1f465&quot;) + &quot; &quot; + c    }    function dsply(e, _show = true) {      _show ? show(e) : hide(e);    }    async function isAudioVolumeRO() {      let a = new Audio();      a.volume = 0.5;      return new Promise(r => setTimeout(() => {            r(!(a.volume == 0.5))      }, 1));    }    const Global = {      inited: false,      NativePostMessageFunction: null,      NativeAttachShadow: null,      NativeFetch: null    }    function AttachShadow(e, options) {      try {            return e.attachShadow(options);      } catch (err) {            GetNativeFunction();            return Global.NativeAttachShadow.call(e, options);      }    }    function GetNativeFunction() {      if (Global.inited) {            return;      }      Global.inited = true;      let temp = document.createElement(&quot;iframe&quot;);      hide(temp);      document.body.append(temp);      Global.NativePostMessageFunction = temp.contentWindow.postMessage;      Global.NativeAttachShadow = temp.contentWindow.Element.prototype.attachShadow;      Global.NativeFetch = temp.contentWindow.fetch;    }    function PostMessage(window, data) {      if (/\{\s+\/.test(Function.prototype.toString.call(window.postMessage))) {            window.postMessage(data, &quot;*&quot;);      } else {            GetNativeFunction();            Global.NativePostMessageFunction.call(window, data, &quot;*&quot;);      }    }    async function Fetch(url, init) {      if (/\{\s+\/.test(Function.prototype.toString.call(window.fetch))) {            return await fetch(url, init);      } else {            GetNativeFunction();            return await Global.NativeFetch.call(window, url, init);      }    }    function sendMessageToTop(type, data) {      PostMessage(window.top, {            source: &quot;VideoTogether&quot;,            type: type,            data: data      });    }    function sendMessageToSelf(type, data) {      PostMessage(window, {            source: &quot;VideoTogether&quot;,            type: type,            data: data      });    }    function sendMessageTo(w, type, data) {      PostMessage(w, {            source: &quot;VideoTogether&quot;,            type: type,            data: data      });    }    function initRangeSlider(slider) {      const min = slider.min      const max = slider.max      const value = slider.value      slider.style.background = `linear-gradient(to right, #1abc9c 0%, #1abc9c ${(value - min) / (max - min) * 100}%, #d7dcdf ${(value - min) / (max - min) * 100}%, #d7dcdf 100%)`      slider.addEventListener('input', function () {            this.style.background = `linear-gradient(to right, #1abc9c 0%, #1abc9c ${(this.value - this.min) / (this.max - this.min) * 100}%, #d7dcdf ${(this.value - this.min) / (this.max - this.min) * 100}%, #d7dcdf 100%)`      });    }    function WSUpdateRoomRequest(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url) {      return {            &quot;method&quot;: &quot;/room/update&quot;,            &quot;data&quot;: {                &quot;tempUser&quot;: extension.tempUser,                &quot;password&quot;: password,                &quot;name&quot;: name,                &quot;playbackRate&quot;: playbackRate,                &quot;currentTime&quot;: currentTime,                &quot;paused&quot;: paused,                &quot;url&quot;: url,                &quot;lastUpdateClientTime&quot;: localTimestamp,                &quot;duration&quot;: duration,                &quot;protected&quot;: isRoomProtected(),                &quot;videoTitle&quot;: extension.isMain ? document.title : extension.videoTitle,                &quot;sendLocalTimestamp&quot;: Date.now() / 1000,                &quot;m3u8Url&quot;: m3u8Url            }      }    }    function WSJoinRoomRequest(name, password) {      return {            &quot;method&quot;: &quot;/room/join&quot;,            &quot;data&quot;: {                &quot;password&quot;: password,                &quot;name&quot;: name,            }      }    }    function WsUpdateMemberRequest(name, password, isLoadding, currentUrl) {      return {            &quot;method&quot;: &quot;/room/update_member&quot;,            &quot;data&quot;: {                &quot;password&quot;: password,                &quot;roomName&quot;: name,                &quot;sendLocalTimestamp&quot;: Date.now() / 1000,                &quot;userId&quot;: extension.tempUser,                &quot;isLoadding&quot;: isLoadding,                &quot;currentUrl&quot;: currentUrl            }      }    }    function popupError(msg) {      let x = select(&quot;#snackbar&quot;);      x.innerHTML = msg;      x.className = &quot;show&quot;;      setTimeout(function () { x.className = x.className.replace(&quot;show&quot;, &quot;&quot;); }, 3000);      let changeVoiceBtn = select('#changeVoiceBtn');      if (changeVoiceBtn != undefined) {            changeVoiceBtn.onclick = () => {                windowPannel.ShowTxtMsgTouchPannel();            }      }    }    async function waitForRoomUuid(timeout = 10000) {      return new Promise((res, rej) => {            let id = setInterval(() => {                if (roomUuid != null) {                  res(roomUuid);                  clearInterval(id);                }            }, 200)            setTimeout(() => {                clearInterval(id);                rej(null);            }, timeout);      });    }    class Room {      constructor() {            this.currentTime = null;            this.duration = null;            this.lastUpdateClientTime = null;            this.lastUpdateServerTime = null;            this.name = null;            this.paused = null;            this.playbackRate = null;            this.protected = null;            this.timestamp = null;            this.url = null;            this.videoTitle = null;            this.waitForLoadding = null;      }    }    const WS = {      _socket: null,      _lastConnectTime: 0,      _connectTimeout: 10,      _expriedTime: 5,      _lastUpdateTime: 0,      _lastErrorMessage: null,      _lastRoom: new Room(),      _connectedToService: false,      isOpen() {            try {                return this._socket.readyState = 1 && this._connectedToService;            } catch { return false; }      },      async connect() {            if (this._socket != null) {                try {                  if (this._socket.readyState == 1) {                        return;                  }                  if (this._socket.readyState == 0                        && this._lastConnectTime + this._connectTimeout > Date.now() / 1000) {                        return;                  }                } catch { }            }            console.log('ws connect');            this._lastConnectTime = Date.now() / 1000            this._connectedToService = false;            try {                this.disconnect()                this._socket = new WebSocket(`wss://${extension.video_together_host.replace(&quot;https://&quot;, &quot;&quot;)}/ws?language=${language}`);                this._socket.onmessage = async e => {                  let lines = e.data.split('\n');                  for (let i = 0; i0) {                  configuration.iceServers = res.data;                  configuration.iceTransportPolicy = 'relay';                }                Voice.conn = new RTCPeerConnection(configuration);                Voice.conn.onicecandidate = ({ candidate }) => {                  rpc('trickle', );                };                Voice.conn.ontrack = (event) => {                  console.log(&quot;ontrack&quot;, event);                  let stream = event.streams;                  let sid = fixedDecodeURIComponent(stream.id);                  let id = sid.split(':');                  // var name = Base64.decode(sid.split(':'));                  console.log(id, uid);                  if (id === uid) {                        return;                  }                  event.track.onmute = (event) => {                        console.log(&quot;onmute&quot;, event);                  };                  let aid = 'peer-audio-' + id;                  let el = select('#' + aid);                  if (el) {                        el.srcObject = stream;                  } else {                        el = document.createElement(event.track.kind)                        el.id = aid;                        el.srcObject = stream;                        el.autoplay = true;                        el.controls = false;                        select('#peer').appendChild(el);                  }                };                try {                  const constraints = {                        audio: {                            echoCancellation: cancellingNoise,                            noiseSuppression: cancellingNoise                        },                        video: false                  };                  Voice.stream = await navigator.mediaDevices.getUserMedia(constraints);                } catch (err) {                  if (Voice.status == VoiceStatus.CONNECTTING) {                        Voice.errorMessage = &quot;麦克风权限获取失败&quot;;                        Voice.status = VoiceStatus.ERROR;                  }                  return;                }                Voice.stream.getTracks().forEach((track) => {                  track.enabled = !mutting;                  Voice.conn.addTrack(track, Voice.stream);                });                await Voice.conn.setLocalDescription(await Voice.conn.createOffer());                res = await rpc('publish', );                if (res.data) {                  let jsep = JSON.parse(res.data.jsep);                  if (jsep.type == 'answer') {                        await Voice.conn.setRemoteDescription(jsep);                        ucid = res.data.track;                        await subscribe(Voice.conn);                  }                } else {                  throw new Error('未知错误');                }                Voice.conn.oniceconnectionstatechange = e => {                  if (Voice.conn.iceConnectionState == &quot;disconnected&quot; || Voice.conn.iceConnectionState == &quot;failed&quot; || Voice.conn.iceConnectionState == &quot;closed&quot;) {                        Voice.errorMessage = &quot;连接断开&quot;;                        Voice.status = VoiceStatus.ERROR;                  } else {                        if (Voice.status == VoiceStatus.ERROR) {                            Voice.status = Voice._mutting ? VoiceStatus.MUTED : VoiceStatus.UNMUTED;                        }                  }                }            }            async function rpc(method, params = [], retryTime = -1) {                try {                  const response = await window.videoTogetherExtension.Fetch(extension.video_together_host + &quot;/kraken&quot;, &quot;POST&quot;, { id: generateUUID(), method: method, params: params }, {                        method: 'POST', // *GET, POST, PUT, DELETE, etc.                        mode: 'cors', // no-cors, *cors, same-origin                        cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached                        credentials: 'omit', // include, *same-origin, omit                        headers: {                            'Content-Type': 'application/json'                        },                        redirect: 'follow', // manual, *follow, error                        referrerPolicy: 'no-referrer', // no-referrer, *client                        body: JSON.stringify({ id: generateUUID(), method: method, params: params }) // body data type must match &quot;Content-Type&quot; header                  });                  return await response.json(); // parses JSON response into native JavaScript objects                } catch (err) {                  if (Voice.status == VoiceStatus.STOP) {                        return;                  }                  if (retryTime == 0) {                        throw err;                  }                  await new Promise(r => setTimeout(r, 1000));                  return await rpc(method, params, retryTime - 1);                }            }      },      stop: () => {            try {                Voice.conn.getSenders().forEach(s => {                  if (s.track) {                        s.track.stop();                  }                });            } catch (e) { };            [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => e.remove());            try {                Voice.conn.close();                delete Voice.conn;            } catch { }            try {                Voice.stream.getTracks().forEach(function (track) {                  track.stop();                });                delete Voice.stream;            } catch { }            Voice.status = VoiceStatus.STOP;      },      mute: () => {            Voice.conn.getSenders().forEach(s => {                if (s.track) {                  s.track.enabled = false;                }            });            Voice._mutting = true;            Voice.status = VoiceStatus.MUTED;      },      unmute: () => {            Voice.conn.getSenders().forEach(s => {                if (s.track) {                  s.track.enabled = true;                }            });            Voice._mutting = false;            Voice.status = VoiceStatus.UNMUTED;      },      updateVoiceSetting: async (cancellingNoise = false) => {            const constraints = {                audio: {                  echoCancellation: cancellingNoise,                  noiseSuppression: cancellingNoise                },                video: false            };            try {                prevStream = Voice.stream;                Voice.stream = await navigator.mediaDevices.getUserMedia(constraints);                Voice.conn.getSenders().forEach(s => {                  if (s.track) {                        s.replaceTrack(Voice.stream.getTracks().find(t => t.kind == s.track.kind));                  }                })                prevStream.getTracks().forEach(t => t.stop());                delete prevStream;            } catch (e) { console.log(e); };      }    }    function generateUUID() {      if (crypto.randomUUID != undefined) {            return crypto.randomUUID();      }      return ( + -1e3 + -4e3 + -8e3 + -1e11).replace(//g, c =>            (c ^ crypto.getRandomValues(new Uint8Array(1)) & 15 >> c / 4).toString(16)      );    }    function generateTempUserId() {      return generateUUID() + &quot;:&quot; + Date.now() / 1000;    }    /**   *   *Base64 encode / decode   *http://www.webtoolkit.info   *   **/    const Base64 = {      // private property      _keyStr: &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&quot;      // public method for encoding      , encode: function (input) {            var output = &quot;&quot;;            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;            var i = 0;            input = Base64._utf8_encode(input);            while (i > 2;                enc2 = ((chr1 & 3) <> 4);                enc3 = ((chr2 & 15) <> 6);                enc4 = chr3 & 63;                if (isNaN(chr2)) {                  enc3 = enc4 = 64;                }                else if (isNaN(chr3)) {                  enc4 = 64;                }                output = output +                  this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +                  this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);            } // Whend            return output;      } // End Function encode      // public method for decoding      , decode: function (input) {            var output = &quot;&quot;;            var chr1, chr2, chr3;            var enc1, enc2, enc3, enc4;            var i = 0;            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, &quot;&quot;);            while (i < input.length) {                enc1 = this._keyStr.indexOf(input.charAt(i++));                enc2 = this._keyStr.indexOf(input.charAt(i++));                enc3 = this._keyStr.indexOf(input.charAt(i++));                enc4 = this._keyStr.indexOf(input.charAt(i++));                chr1 = (enc1 <> 4);                chr2 = ((enc2 & 15) <> 2);                chr3 = ((enc3 & 3) << 6) | enc4;                output = output + String.fromCharCode(chr1);                if (enc3 != 64) {                  output = output + String.fromCharCode(chr2);                }                if (enc4 != 64) {                  output = output + String.fromCharCode(chr3);                }            } // Whend            output = Base64._utf8_decode(output);            return output;      } // End Function decode      // private method for UTF-8 encoding      , _utf8_encode: function (string) {            var utftext = &quot;&quot;;            string = string.replace(/\r\n/g, &quot;\n&quot;);            for (var n = 0; n < string.length; n++) {                var c = string.charCodeAt(n);                if (c127) && (c > 6) | 192);                  utftext += String.fromCharCode((c & 63) | 128);                }                else {                  utftext += String.fromCharCode((c >> 12) | 224);                  utftext += String.fromCharCode(((c >> 6) & 63) | 128);                  utftext += String.fromCharCode((c & 63) | 128);                }            } // Next n            return utftext;      } // End Function _utf8_encode      // private method for UTF-8 decoding      , _utf8_decode: function (utftext) {            var string = &quot;&quot;;            var i = 0;            var c, c1, c2, c3;            c = c1 = c2 = 0;            while (i < utftext.length) {                c = utftext.charCodeAt(i);                if (c191) && (c < 224)) {                  c2 = utftext.charCodeAt(i + 1);                  string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));                  i += 2;                }                else {                  c2 = utftext.charCodeAt(i + 1);                  c3 = utftext.charCodeAt(i + 2);                  string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) < {                if (getEnableMiniBar() && getEnableTextMessage() && document.fullscreenElement != undefined                  && (extension.ctxRole == extension.RoleEnum.Master || extension.ctxRole == extension.RoleEnum.Member)) {                  const qs = (s) => this.fullscreenWrapper.querySelector(s);                  try {                        qs(&quot;#memberCount&quot;).innerText = extension.ctxMemberCount;                        qs(&quot;#send-button&quot;).disabled = !extension.ctxWsIsOpen;                  } catch { };                  if (document.fullscreenElement.contains(this.fullscreenSWrapper)) {                        return;                  }                  let shadowWrapper = document.createElement(&quot;div&quot;);                  this.fullscreenSWrapper = shadowWrapper;                  shadowWrapper.id = &quot;VideoTogetherfullscreenSWrapper&quot;;                  let wrapper;                  try {                        wrapper = AttachShadow(shadowWrapper, { mode: &quot;open&quot; });                        wrapper.addEventListener('keydown', (e) => e.stopPropagation());                        this.fullscreenWrapper = wrapper;                  } catch (e) { console.error(e); }                  wrapper.innerHTML = `    &lt;            &#128101;      0   
    x      发送
`;                  document.fullscreenElement.appendChild(shadowWrapper);                  var container = wrapper.getElementById('container');                  let expandBtn = wrapper.getElementById('expand-button');                  let msgInput = wrapper.getElementById('text-input');                  let sendBtn = wrapper.getElementById('send-button');                  let closeBtn = wrapper.getElementById('close-btn');                  let expanded = true;                  function expand() {                        if (expanded) {                            expandBtn.innerText = '>'                            sendBtn.style.display = 'none';                            msgInput.classList.remove('expand');                        } else {                            expandBtn.innerText = ' { shadowWrapper.style.display = &quot;none&quot;; }                  wrapper.getElementById('expand-button').addEventListener('click', () => expand());                  sendBtn.onclick = () => {                        extension.currentSendingMsgId = generateUUID();                        sendMessageToTop(MessageType.SendTxtMsg, { currentSendingMsgId: extension.currentSendingMsgId, value: msgInput.value });                  }                  GotTxtMsgCallback = (id, msg) => {                        console.log(id, msg);                        if (id == extension.currentSendingMsgId && msg == msgInput.value) {                            msgInput.value = &quot;&quot;;                        }                  }                  msgInput.addEventListener(&quot;keyup&quot;, e => {                        if (e.key == &quot;Enter&quot;) {                            sendBtn.click();                        }                  });                } else {                  if (this.fullscreenSWrapper != undefined) {                        this.fullscreenSWrapper.remove();                        this.fullscreenSWrapper = undefined;                        this.fullscreenWrapper = undefined;                        GotTxtMsgCallback = undefined;                  }                }            }, 500);            if (this.isMain) {                document.addEventListener(&quot;click&quot;, () => {                  this.enableSpeechSynthesis();                });                this.minimized = false;                let shadowWrapper = document.createElement(&quot;div&quot;);                shadowWrapper.id = &quot;VideoTogetherWrapper&quot;;                shadowWrapper.ontouchstart = (e) => { e.stopPropagation() }                let wrapper;                try {                  wrapper = AttachShadow(shadowWrapper, { mode: &quot;open&quot; });                  wrapper.addEventListener('keydown', (e) => e.stopPropagation())                } catch (e) { console.error(e); }                this.shadowWrapper = shadowWrapper;                this.wrapper = wrapper;                wrapper.innerHTML = `
                  VideoTogether
   
                                                                                                                                                                  https://afdian.net/a/videotogether&quot; target=&quot;_blank&quot; id=&quot;vtDonate&quot; type=&quot;button&quot;      class=&quot;vt-modal-donate vt-modal-title-button&quot;]                                                          https://setting.2gether.video/&quot; target=&quot;_blank&quot; id=&quot;videoTogetherSetting&quot; type=&quot;button&quot;      aria-label=&quot;Setting&quot; class=&quot;vt-modal-setting vt-modal-title-button&quot;]                                                                                                                           
                                                
      
                  房间                  
                  密码                  
                                                      发送                     
                      连接文字聊天服务器中...            缺少中文语音包         
      
      
                        检测视频中...                      确认并下载                                                                                    
            下载中,不要关闭页面            下载完成         
      
                  https://local.2gether.video/local_videos.zh-cn.html&quot;]查看已下载视频          https://local.2gether.video/about.zh-cn.html&quot;]版权说明      
      
                      视频音量                              
      
                通话音量                              
      
                  IOS不支持音量调节
      
            
   
   
                            建 房                        加 入            
                        退 出                        通 话                        
         
         
         
      
                                                                                                                                                                                                         
                                                                                                                                                                                                                                          帮 助         



`;                (document.body || document.documentElement).appendChild(shadowWrapper);                wrapper.querySelector(&quot;#videoTogetherMinimize&quot;).onclick = () => { this.Minimize() }                wrapper.querySelector(&quot;#videoTogetherMaximize&quot;).onclick = () => { this.Maximize() }                [&quot;&quot;, &quot;webkit&quot;].forEach(prefix => {                  document.addEventListener(prefix + &quot;fullscreenchange&quot;, (event) => {                        if (document.fullscreenElement || document.webkitFullscreenElement) {                            hide(this.videoTogetherFlyPannel);                            hide(this.videoTogetherSamllIcon);                        } else {                            if (this.minimized) {                              this.Minimize();                            } else {                              this.Maximize();                            }                        }                  });                });                wrapper.querySelector(&quot;#textMessageInput&quot;).addEventListener(&quot;keyup&quot;, e => {                  if (e.key == &quot;Enter&quot;) {                        wrapper.querySelector(&quot;#textMessageSend&quot;).click();                  }                });                wrapper.querySelector(&quot;#textMessageSend&quot;).onclick = async () => {                  extension.currentSendingMsgId = generateUUID();                  WS.sendTextMessage(extension.currentSendingMsgId, select(&quot;#textMessageInput&quot;).value);                }                this.lobbyBtnGroup = wrapper.querySelector(&quot;#lobbyBtnGroup&quot;);                this.createRoomButton = wrapper.querySelector('#videoTogetherCreateButton');                this.joinRoomButton = wrapper.querySelector(&quot;#videoTogetherJoinButton&quot;);                this.roomButtonGroup = wrapper.querySelector('#roomButtonGroup');                this.exitButton = wrapper.querySelector(&quot;#videoTogetherExitButton&quot;);                this.callBtn = wrapper.querySelector(&quot;#callBtn&quot;);                this.callBtn.onclick = () => Voice.join(&quot;&quot;, window.videoTogetherExtension.roomName);                this.helpButton = wrapper.querySelector(&quot;#videoTogetherHelpButton&quot;);                this.audioBtn = wrapper.querySelector(&quot;#audioBtn&quot;);                this.micBtn = wrapper.querySelector(&quot;#micBtn&quot;);                this.videoVolume = wrapper.querySelector(&quot;#videoVolume&quot;);                this.callVolumeSlider = wrapper.querySelector(&quot;#callVolume&quot;);                this.callErrorBtn = wrapper.querySelector(&quot;#callErrorBtn&quot;);                this.easyShareCopyBtn = wrapper.querySelector(&quot;#easyShareCopyBtn&quot;);                this.textMessageChat = wrapper.querySelector(&quot;#textMessageChat&quot;);                this.textMessageConnecting = wrapper.querySelector(&quot;#textMessageConnecting&quot;);                this.textMessageConnectingStatus = wrapper.querySelector(&quot;#textMessageConnectingStatus&quot;);                this.zhcnTtsMissing = wrapper.querySelector(&quot;#zhcnTtsMissing&quot;);                this.downloadBtn = wrapper.querySelector(&quot;#downloadBtn&quot;);                hide(this.downloadBtn);                this.confirmDownloadBtn = wrapper.querySelector(&quot;#confirmDownloadBtn&quot;)                this.confirmDownloadBtn.onclick = () => {                  if (extension.downloadM3u8UrlType == &quot;video&quot;) {                        extension.Fetch(extension.video_together_host + &quot;/beta/counter?key=confirm_video_download&quot;)                        console.log(extension.downloadM3u8Url, extension.downloadM3u8UrlType)                        sendMessageToTop(MessageType.SetStorageValue, {                            key: &quot;PublicNextDownload&quot;, value: {                              filename: document.title + '.mp4',                              url: extension.downloadM3u8Url                            }                        });                        const a = document.createElement(&quot;a&quot;);                        a.href = extension.downloadM3u8Url;                        a.target = &quot;_blank&quot;;                        a.download = document.title + &quot;.mp4&quot;;                        a.click();                        return;                  }                  extension.Fetch(extension.video_together_host + &quot;/beta/counter?key=confirm_m3u8_download&quot;)                  isDownloading = true;                  const m3u8url = extension.downloadM3u8Url                  sendMessageTo(extension.m3u8PostWindows, MessageType.StartDownload, {                        m3u8Url: m3u8url,                        m3u8Content: extension.GetM3u8Content(m3u8url),                        urls: extension.GetAllM3u8SegUrls(m3u8url),                        title: document.title,                        pageUrl: window.location.href                  });                  hide(this.confirmDownloadBtn);                  show(select(&quot;#downloadProgress&quot;));                }                this.downloadBtn.onclick = () => {                  setInterval(() => {                        if (isDownloading) {                            return;                        }                        if (extension.downloadM3u8Url != undefined) {                            show(this.confirmDownloadBtn);                            select('#downloadVideoInfo').innerText = getDurationStr(extension.downloadDuration);                        } else {                            hide(this.confirmDownloadBtn);                            select('#downloadVideoInfo').innerText = &quot;检测视频中...&quot;                        }                  }, 1000);                  inDownload = true;                  this.inputRoomName.value = &quot;download_&quot; + generateUUID();                  this.createRoomButton.click()                  hide(select('.vt-modal-footer'))                  hide(select('#mainPannel'))                  show(select('#downloadPannel'))                }                this.easyShareCopyBtn.onclick = async () => {                  try {                        if (isWeb()) {                            await navigator.clipboard.writeText(extension.linkWithMemberState(window.location, extension.RoleEnum.Member, false))                        } else {                            await navigator.clipboard.writeText(&quot;点击链接,和我一起看吧: , 如果打不开可以尝试备用链接:&quot;                              .replace(&quot;&quot;, extension.generateEasyShareLink())                              .replace(&quot;&quot;, extension.generateEasyShareLink(true)));                        }                        popupError(&quot;复制成功,快去分享吧&quot;);                  } catch {                        popupError(&quot;复制失败&quot;);                  }                }                this.callErrorBtn.onclick = () => {                  Voice.join(&quot;&quot;, window.videoTogetherExtension.roomName);                }                this.videoVolume.oninput = () => {                  extension.videoVolume = this.videoVolume.value;                  sendMessageToTop(MessageType.ChangeVideoVolume, { volume: extension.getVideoVolume() / 100 });                }                this.callVolumeSlider.oninput = () => {                  extension.voiceVolume = this.callVolumeSlider.value;                  [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => {                        e.volume = extension.getVoiceVolume() / 100;                  });                }                initRangeSlider(this.videoVolume);                initRangeSlider(this.callVolumeSlider);                this.audioBtn.onclick = async () => {                  let hideMain = select('#mainPannel').style.display == 'none';                  dsply(select('#mainPannel'), hideMain);                  dsply(select('#voicePannel'), !hideMain);                  if (!hideMain) {                        this.audioBtn.style.color = '#1890ff';                  } else {                        this.audioBtn.style.color = '#6c6c6c';                  }                  if (await isAudioVolumeRO()) {                        show(select('#iosVolumeErr'));                        hide(select('#videoVolumeCtrl'));                        hide(select('#callVolumeCtrl'));                  }                }                this.micBtn.onclick = async () => {                  switch (Voice.status) {                        case VoiceStatus.STOP: {                            // TODO need fix                            await Voice.join();                            break;                        }                        case VoiceStatus.UNMUTED: {                            Voice.mute();                            break;                        }                        case VoiceStatus.MUTED: {                            Voice.unmute();                            break;                        }                  }                }                this.createRoomButton.onclick = this.CreateRoomButtonOnClick.bind(this);                this.joinRoomButton.onclick = this.JoinRoomButtonOnClick.bind(this);                this.helpButton.onclick = this.HelpButtonOnClick.bind(this);                this.exitButton.onclick = (() => {                  window.videoTogetherExtension.exitRoom();                });                this.videoTogetherRoleText = wrapper.querySelector(&quot;#videoTogetherRoleText&quot;)                this.videoTogetherSetting = wrapper.querySelector(&quot;#videoTogetherSetting&quot;);                hide(this.videoTogetherSetting);                this.inputRoomName = wrapper.querySelector('#videoTogetherRoomNameInput');                this.inputRoomPassword = wrapper.querySelector(&quot;#videoTogetherRoomPdIpt&quot;);                this.inputRoomNameLabel = wrapper.querySelector('#videoTogetherRoomNameLabel');                this.inputRoomPasswordLabel = wrapper.querySelector(&quot;#videoTogetherRoomPasswordLabel&quot;);                this.videoTogetherHeader = wrapper.querySelector(&quot;#videoTogetherHeader&quot;);                this.videoTogetherFlyPannel = wrapper.getElementById(&quot;videoTogetherFlyPannel&quot;);                this.videoTogetherSamllIcon = wrapper.getElementById(&quot;videoTogetherSamllIcon&quot;);                this.volume = 1;                this.statusText = wrapper.querySelector(&quot;#videoTogetherStatusText&quot;);                this.InLobby(true);                this.Init();                setInterval(() => {                  this.ShowPannel();                }, 1000);            }            try {                document.querySelector(&quot;#videoTogetherLoading&quot;).remove()            } catch { }      }      ShowTxtMsgTouchPannel() {            try {                function exitFullScreen() {                  if (document.exitFullscreen) {                        document.exitFullscreen();                  } else if (document.webkitExitFullscreen) { /* Safari */                        document.webkitExitFullscreen();                  } else if (document.mozCancelFullScreen) { /* Firefox */                        document.mozCancelFullScreen();                  }                }                exitFullScreen();            } catch { }            try {                this.txtMsgTouchPannel.remove();            } catch { }            this.txtMsgTouchPannel = document.createElement('div');            let touch = this.txtMsgTouchPannel;            touch.id = &quot;videoTogetherTxtMsgTouch&quot;;            touch.style.width = &quot;100%&quot;;            touch.style.height = &quot;100%&quot;;            touch.style.position = &quot;fixed&quot;;            touch.style.top = &quot;0&quot;;            touch.style.left = &quot;0&quot;;            touch.style.zIndex = &quot;2147483647&quot;;            touch.style.background = &quot;#fff&quot;;            touch.style.display = &quot;flex&quot;;            touch.style.justifyContent = &quot;center&quot;;            touch.style.alignItems = &quot;center&quot;;            touch.style.padding = &quot;0px&quot;;            touch.style.flexDirection = &quot;column&quot;;            touch.style.lineHeight = &quot;40px&quot;;            AttachShadow(this.txtMsgTouchPannel, { mode: &quot;open&quot; })            touch.addEventListener('click', function () {                windowPannel.enableSpeechSynthesis();                document.body.removeChild(touch);                windowPannel.txtMsgTouchPannel = undefined;            });            document.body.appendChild(touch);            this.setTxtMsgTouchPannelText(&quot;VideoTogether: 您有一条新消息,点击屏幕接收&quot;);      }      setTxtMsgInterface(type) {            hide(this.textMessageChat);            hide(this.textMessageConnecting);            hide(this.textMessageConnectingStatus);            hide(this.zhcnTtsMissing);            if (type == 0) {            }            if (type == 1) {                show(this.textMessageChat);            }            if (type == 2) {                show(this.textMessageConnecting);                this.textMessageConnectingStatus.innerText = &quot;连接文字聊天服务器中...&quot;                show(this.textMessageConnectingStatus);            }            if (type == 3) {                show(this.textMessageConnecting);                show(this.zhcnTtsMissing);            }            if (type == 4) {                show(this.textMessageConnecting);                this.textMessageConnectingStatus.innerText = &quot;文字聊天已关闭&quot;                show(this.textMessageConnectingStatus);            }      }      enableSpeechSynthesis() {            if (!extension.speechSynthesisEnabled) {                try {                  extension.gotTextMsg(&quot;&quot;, &quot;&quot;, true);                  extension.speechSynthesisEnabled = true;                } catch { }            }      }      setTxtMsgTouchPannelText(s) {            let span = document.createElement('span');            span.style.fontSize = &quot;40px&quot;;            span.style.lineHeight = &quot;40px&quot;;            span.style.color = &quot;black&quot;;            span.style.overflowWrap = &quot;break-word&quot;;            span.style.textAlign = &quot;center&quot;;            span.textContent = s;            this.txtMsgTouchPannel.shadowRoot.appendChild(span);            let voiceSelect = document.createElement('select');            this.voiceSelect = voiceSelect;            voiceSelect.onclick = (e) => {                e.stopPropagation();            }            let label = span.cloneNode(true);            label.textContent = &quot;你可以在下方选择朗读信息的语音:&quot;;            this.txtMsgTouchPannel.shadowRoot.appendChild(document.createElement('br'));            this.txtMsgTouchPannel.shadowRoot.appendChild(label);            let voices = speechSynthesis.getVoices();            voices.forEach(function (voice, index) {                var option = document.createElement('option');                option.value = voice.voiceURI;                option.textContent = voice.name + ' (' + voice.lang + ')';                voiceSelect.appendChild(option);            });            voiceSelect.oninput = (e) => {                console.log(e);                sendMessageToTop(MessageType.SetStorageValue, { key: &quot;PublicMessageVoice&quot;, value: voiceSelect.value });            }            voiceSelect.style.fontSize = &quot;20px&quot;;            voiceSelect.style.height = &quot;50px&quot;;            voiceSelect.style.maxWidth = &quot;100%&quot;;            try {                if (window.VideoTogetherStorage.PublicMessageVoice != undefined) {                  voiceSelect.value = window.VideoTogetherStorage.PublicMessageVoice;                } else {                  voiceSelect.value = speechSynthesis.getVoices().find(v => v.default).voiceURI;                }            } catch { };            this.txtMsgTouchPannel.shadowRoot.appendChild(voiceSelect)      }      ShowPannel() {            if (!document.documentElement.contains(this.shadowWrapper)) {                (document.body || document.documentElement).appendChild(this.shadowWrapper);            }      }      Minimize(isDefault = false) {            this.minimized = true;            if (!isDefault) {                this.SaveIsMinimized(true);            }            this.disableDefaultSize = true;            hide(this.videoTogetherFlyPannel);            show(this.videoTogetherSamllIcon);      }      Maximize(isDefault = false) {            this.minimized = false;            if (!isDefault) {                this.SaveIsMinimized(false);            }            this.disableDefaultSize = true;            show(this.videoTogetherFlyPannel);            hide(this.videoTogetherSamllIcon);      }      SaveIsMinimized(minimized) {            localStorage.setItem(&quot;VideoTogetherMinimizedHere&quot;, minimized ? 1 : 0)      }      Init() {            let VideoTogetherMinimizedHere = localStorage.getItem(&quot;VideoTogetherMinimizedHere&quot;);            if (VideoTogetherMinimizedHere == 0) {                this.Maximize(true);            } else if (VideoTogetherMinimizedHere == 1) {                this.Minimize(true);            }      }      InRoom() {            try {                speechSynthesis.getVoices();            } catch { };            this.Maximize();            this.inputRoomName.disabled = true;            hide(this.lobbyBtnGroup)            show(this.roomButtonGroup);            this.exitButton.style = &quot;&quot;;            hide(this.inputRoomPasswordLabel);            hide(this.inputRoomPassword);            this.inputRoomName.placeholder = &quot;&quot;;            this.isInRoom = true;            hide(this.downloadBtn)      }      InLobby(init = false) {            if (!init) {                this.Maximize();            }            this.inputRoomName.disabled = false;            this.inputRoomPasswordLabel.style.display = &quot;inline-block&quot;;            this.inputRoomPassword.style.display = &quot;inline-block&quot;;            this.inputRoomName.placeholder = &quot;请输入房间名&quot;            show(this.lobbyBtnGroup);            hide(this.roomButtonGroup);            hide(this.easyShareCopyBtn);            this.setTxtMsgInterface(0);            dsply(this.downloadBtn, downloadEnabled())            this.isInRoom = false;      }      CreateRoomButtonOnClick() {            this.Maximize();            let roomName = this.inputRoomName.value;            let password = this.inputRoomPassword.value;            window.videoTogetherExtension.CreateRoom(roomName, password);      }      JoinRoomButtonOnClick() {            this.Maximize();            let roomName = this.inputRoomName.value;            let password = this.inputRoomPassword.value;            window.videoTogetherExtension.JoinRoom(roomName, password);      }      HelpButtonOnClick() {            this.Maximize();            let url = 'https://2gether.video/guide/qa.html';            if (vtRuntime == &quot;website&quot;) {                url = &quot;https://2gether.video/guide/website_qa.html&quot;            }            window.open(url, '_blank');      }      UpdateStatusText(text, color) {            this.statusText.innerHTML = text;            this.statusText.style.color = color;      }    }    class VideoModel {      constructor(id, duration, activatedTime, refreshTime, priority = 0) {            this.id = id;            this.duration = duration;            this.activatedTime = activatedTime;            this.refreshTime = refreshTime;            this.priority = priority;      }    }    let MessageType = {      ActivatedVideo: 1,      ReportVideo: 2,      SyncMemberVideo: 3,      SyncMasterVideo: 4,      UpdateStatusText: 5,      JumpToNewPage: 6,      GetRoomData: 7,      ChangeVoiceVolume: 8,      ChangeVideoVolume: 9,      FetchRequest: 13,      FetchResponse: 14,      SetStorageValue: 15,      SyncStorageValue: 16,      ExtensionInitSuccess: 17,      SetTabStorage: 18,      SetTabStorageSuccess: 19,      UpdateRoomRequest: 20,      CallScheduledTask: 21,      RoomDataNotification: 22,      UpdateMemberStatus: 23,      TimestampV2Resp: 24,      // EasyShareCheckSucc: 25,      FetchRealUrlReq: 26,      FetchRealUrlResp: 27,      FetchRealUrlFromIframeReq: 28,      FetchRealUrlFromIframeResp: 29,      SendTxtMsg: 30,      GotTxtMsg: 31,      StartDownload: 32,      DownloadStatus: 33,      UpdateM3u8Files: 1001,      SaveIndexedDb: 2001,      ReadIndexedDb: 2002,      SaveIndexedDbResult: 2003,      ReadIndexedDbResult: 2004,      RegexMatchKeysDb: 2005,      RegexMatchKeysDbResult: 2006,      DeleteFromIndexedDb: 2007,      DeleteFromIndexedDbResult: 2008,      StorageEstimate: 2009,      StorageEstimateResult: 2010,      ReadIndexedDbSw: 2011,      ReadIndexedDbSwResult: 2012,      //2013 used      IosStorageSet: 3001,      IosStorageSetResult: 3002,      IosStorageGet: 3003,      IosStorageGetResult: 3004,      IosStorageDelete: 3005,      IosStorageDeleteResult: 3006,      IosStorageUsage: 3007,      IosStorageUsageResult: 3008,      IosStorageCompact: 3009,      IosStorageDeletePrefix: 3010,      IosStorageDeletePrefixResult: 3011,    }    let VIDEO_EXPIRED_SECOND = 10    class VideoWrapper {      set currentTime(v) {            this.currentTimeSetter(v);      }      get currentTime() {            return this.currentTimeGetter();      }      set playbackRate(v) {            this.playbackRateSetter(v);      }      get playbackRate() {            return this.playbackRateGetter();      }      constructor(play, pause, paused, currentTimeGetter, currentTimeSetter, duration, playbackRateGetter, playbackRateSetter) {            this.play = play;            this.pause = pause;            this.paused = paused;            this.currentTimeGetter = currentTimeGetter;            this.currentTimeSetter = currentTimeSetter;            this.duration = duration;            this.playbackRateGetter = playbackRateGetter;            this.playbackRateSetter = playbackRateSetter;      }    }    class VideoTogetherExtension {      constructor() {            this.RoleEnum = {                Null: 1,                Master: 2,                Member: 3,            }            this.cspBlockedHost = {};            this.video_together_host = 'https://vt.panghair.com:5000/';            this.video_together_main_host = 'https://vt.panghair.com:5000/';            this.video_together_backup_host = 'https://api.chizhou.in/';            this.video_tag_names = [&quot;video&quot;, &quot;bwp-video&quot;, &quot;fake-iframe-video&quot;]            this.timer = 0            this.roomName = &quot;&quot;            this.roomPassword = &quot;&quot;            this.role = this.RoleEnum.Null            this.url = &quot;&quot;            this.duration = undefined            this.waitForLoadding = false;            this.playAfterLoadding = false;            this.minTrip = 1e9;            this.timeOffset = 0;            this.lastScheduledTaskTs = 0;            this.httpSucc = false;            this.activatedVideo = undefined;            this.tempUser = generateTempUserId();            this.version = '1707141762';            this.isMain = (window.self == window.top);            this.UserId = undefined;            this.callbackMap = new Map;            this.allLinksTargetModified = false;            this.voiceVolume = null;            this.videoVolume = null;            this.m3u8Files = {};            this.m3u8DurationReCal = {};            this.m3u8UrlTestResult = {};            this.hasCheckedM3u8Url = {};            this.m3u8PostWindows = {};            this.m3u8MediaUrls = {};            this.currentM3u8Url = undefined;            this.ctxMemberCount = 0;            this.downloadSpeedMb = 0;            this.downloadPercentage = 0;            this.currentSendingMsgId = null;            this.isIos = undefined;            this.speechSynthesisEnabled = false;            // we need a common callback function to deal with all message            this.SetTabStorageSuccessCallback = () => { };            document.addEventListener(&quot;securitypolicyviolation&quot;, (e) => {                let host = (new URL(e.blockedURI)).host;                this.cspBlockedHost = true;            });            try {                this.CreateVideoDomObserver();            } catch { }            this.timer = setInterval(() => this.ScheduledTask(true), 2 * 1000);            this.videoMap = new Map();            window.addEventListener('message', message => {                if (message.data.context) {                  this.tempUser = message.data.context.tempUser;                  this.videoTitle = message.data.context.videoTitle;                  this.voiceStatus = message.data.context.voiceStatus;                  this.timeOffset = message.data.context.timeOffset;                  this.ctxRole = message.data.context.ctxRole;                  this.ctxMemberCount = message.data.context.ctxMemberCount;                  this.ctxWsIsOpen = message.data.context.ctxWsIsOpen;                  // sub frame has 2 storage data source, top frame or extension.js in this frame                  // this 2 data source should be same.                  window.VideoTogetherStorage = message.data.context.VideoTogetherStorage;                }                this.processReceivedMessage(message.data.type, message.data.data, message);            });            try {                navigator.serviceWorker.addEventListener('message', (message) => {                  console.log(`Received a message from service worker: ${event.data}`);                  this.processReceivedMessage(message.data.type, message.data.data, message);                });            } catch { };            // if some element's click be invoked frequenctly, a lot of http request will be sent            // window.addEventListener('click', message => {            //   setTimeout(this.ScheduledTask.bind(this), 200);            // })            if (this.isMain) {                try {                  try {                        this.RecoveryState();                  } catch { }                  this.EnableDraggable();                  setTimeout(() => {                        let allDoms = document.querySelectorAll(&quot;*&quot;);                        for (let i = 0; ispeechSynthesis.getVoices().length) {                return;            }            if (!prepare && !extension.speechSynthesisEnabled) {                windowPannel.ShowTxtMsgTouchPannel();                for (let i = 0; isetTimeout(r, 100));                }            }            try {                if (id == this.currentSendingMsgId && msg == select(&quot;#textMessageInput&quot;).value) {                  select(&quot;#textMessageInput&quot;).value = &quot;&quot;;                }            } catch { }            let ssu = new SpeechSynthesisUtterance();            ssu.text = msg;            ssu.volume = 1;            ssu.rate = 1;            ssu.pitch = 1;            if (idx == -1) {                try {                  ssu.voice = speechSynthesis.getVoices().find(v => v.voiceURI == window.VideoTogetherStorage.PublicMessageVoice);                } catch { }            } else {                ssu.voice = speechSynthesis.getVoices();            }            if (!prepare) {                let startTs = 0;                ssu.onstart = (e => { startTs = e.timeStamp });                ssu.onend = (e => {                  const duration = e.timeStamp - startTs;                  if (duration{                window.videoTogetherFlyPannel.videoTogetherRoleText.innerHTML = text;            }            this.role = role            switch (role) {                case this.RoleEnum.Master:                  setRoleText(&quot;房主&quot;);                  break;                case this.RoleEnum.Member:                  setRoleText(&quot;成员&quot;);                  break;                default:                  setRoleText(&quot;&quot;);                  break;            }      }      generateEasyShareLink(china = false) {            if (china) {                return ''            } else {                return `https://2gether.video/${language}/easyshare.html?VideoTogetherRole=3&VideoTogetherRoomName=${this.roomName}&VideoTogetherTimestamp=9999999999&VideoTogetherUrl=&VideoTogetherPassword=${this.password}`;            }      }      async Fetch(url, method = 'GET', data = null) {            if (!extension.isMain) {                console.error(&quot;fetch in child&quot;);                throw new Error(&quot;fetch in child&quot;);            }            url = new URL(url);            url.searchParams.set(&quot;version&quot;, this.version);            try {                url.searchParams.set(&quot;language&quot;, language);                url.searchParams.set(&quot;voiceStatus&quot;, this.isMain ? Voice.status : this.voiceStatus);                url.searchParams.set(&quot;loaddingVersion&quot;, window.VideoTogetherStorage.LoaddingVersion);                url.searchParams.set(&quot;runtimeType&quot;, window.VideoTogetherStorage.UserscriptType);            } catch (e) { }            try {                url.searchParams.set(&quot;userId&quot;, window.VideoTogetherStorage.PublicUserId);            } catch (e) { }            url = url.toString();            let host = (new URL(url)).host;            if (this.cspBlockedHost || url.startsWith('http:')) {                let id = generateUUID()                return await new Promise((resolve, reject) => {                  this.callbackMap.set(id, (data) => {                        if (data.data) {                            resolve({ json: () => data.data, status: 200 });                        } else {                            reject(new Error(data.error));                        }                        this.callbackMap.delete(id);                  })                  sendMessageToTop(MessageType.FetchRequest, {                        id: id,                        url: url.toString(),                        method: method,                        data: data,                  });                  setTimeout(() => {                        try {                            if (this.callbackMap.has(id)) {                              this.callbackMap.get(id)({ error: &quot;超时&quot; });                            }                        } finally {                            this.callbackMap.delete(id);                        }                  }, 20000);                });            }            try {                if (/\{\s+\/.test(Function.prototype.toString.call(window.fetch))) {                  const controller = new AbortController();                  const timeoutId = setTimeout(() => controller.abort(), 10000);                  return await window.fetch(url, {                        method: method,                        body: data == null ? undefined : JSON.stringify(data),                        signal: controller.signal                  });                } else {                  GetNativeFunction();                  const controller = new AbortController();                  const timeoutId = setTimeout(() => controller.abort(), 10000);                  return await Global.NativeFetch.call(window, url, {                        method: method,                        body: data == null ? undefined : JSON.stringify(data),                        signal: controller.signal                  });                }            } catch (e) {                const host = new URL(extension.video_together_host);                const requestUrl = new URL(url);                if (host.hostname == requestUrl.hostname) {                  extension.httpSucc = false;                }                throw e;            }      }      async ForEachVideo(func) {            try {                if (window.location.hostname.endsWith(&quot;iqiyi.com&quot;)) {                  let video = document.querySelector('.iqp-player-videolayer-inner > video');                  if (video != null) {                        video.VideoTogetherChoosed = true;                        try { await func(video) } catch { };                  }                }                // disneyplus                if (window.location.hostname.endsWith(&quot;disneyplus.com&quot;)) {                  try {                        let ff = document.querySelector('.ff-10sec-icon');                        let rr = document.querySelector('.rwd-10sec-icon');                        let video = document.querySelector('video');                        if (ff && rr && video) {                            if (!video.videoTogetherVideoWrapper) {                              video.videoTogetherVideoWrapper = new VideoWrapper();                            }                            let videoWrapper = video.videoTogetherVideoWrapper;                            videoWrapper.play = async () => await video.play();                            videoWrapper.pause = async () => await video.pause();                            videoWrapper.paused = video.paused                            videoWrapper.currentTimeGetter = () => video.currentTime;                            videoWrapper.currentTimeSetter = (v) => {                              let isFf = v > video.currentTime;                              let d = Math.abs(v - video.currentTime);                              let clickTime = parseInt(d / 10);                              if (clickTime > 0) {                                    console.log(clickTime);                              }                              for (let i = 0; i{                                    isFf ? ff.click() : rr.click();                                    if (!isVideoLoadded(video)) {                                        console.log(&quot;loading&quot;);                                        ff.click();                                        rr.click();                                    }                                    setTimeout(() => {                                        if (isVideoLoadded(video)) {                                          video.currentTime = v;                                        }                                    }, 100);                              }, 200);                            }                            videoWrapper.duration = video.duration;                            videoWrapper.playbackRateGetter = () => video.playbackRate;                            videoWrapper.playbackRateSetter = (v) => { video.playbackRate = v };                            await func(videoWrapper);                        }                  } catch (e) { }                }                // Netflix                if (window.location.hostname.endsWith(&quot;netflix.com&quot;)) {                  try {                        let videoPlayer = netflix.appContext.state.playerApp.getAPI().videoPlayer;                        let player = videoPlayer.getVideoPlayerBySessionId(videoPlayer.getAllPlayerSessionIds());                        if (!player.videoTogetherVideoWrapper) {                            player.videoTogetherVideoWrapper = new VideoWrapper();                        }                        let videoWrapper = player.videoTogetherVideoWrapper;                        videoWrapper.play = async () => await player.play();                        videoWrapper.pause = async () => await player.pause();                        videoWrapper.paused = player.isPaused()                        videoWrapper.currentTimeGetter = () => player.getCurrentTime() / 1000;                        videoWrapper.currentTimeSetter = (v) => player.seek(1000 * v);                        videoWrapper.duration = player.getDuration() / 1000;                        videoWrapper.playbackRateGetter = () => player.getPlaybackRate();                        videoWrapper.playbackRateSetter = (v) => { player.setPlaybackRate(v) };                        await func(videoWrapper);                  } catch (e) { }                }                // 百度网盘                if (window.location.host.includes('pan.baidu.com')) {                  if (!this.BaiduPanPlayer) {                        try {                            if (document.querySelector('.vjs-controls-enabled').player != undefined) {                              this.BaiduPanPlayer = document.querySelector('.vjs-controls-enabled').player;                            }                        } catch { }                  }                  if (this.BaiduPanPlayer) {                        if (!this.BaiduPanPlayer.videoTogetherVideoWrapper) {                            this.BaiduPanPlayer.videoTogetherVideoWrapper = new VideoWrapper();                        }                        let videoWrapper = this.BaiduPanPlayer.videoTogetherVideoWrapper;                        videoWrapper.play = async () => await this.BaiduPanPlayer.play();                        videoWrapper.pause = async () => await this.BaiduPanPlayer.pause();                        videoWrapper.paused = this.BaiduPanPlayer.paused();                        videoWrapper.currentTimeGetter = () => this.BaiduPanPlayer.currentTime();                        videoWrapper.currentTimeSetter = (v) => this.BaiduPanPlayer.currentTime(v);                        videoWrapper.duration = this.BaiduPanPlayer.duration();                        videoWrapper.playbackRateGetter = () => this.BaiduPanPlayer.playbackRate();                        videoWrapper.playbackRateSetter = (v) => this.BaiduPanPlayer.playbackRate(v);                        await func(videoWrapper);                  }                }            } catch (e) { }            try {                // 腾讯视频                if (window.__PLAYER__ != undefined) {                  if (window.__PLAYER__.videoTogetherVideoWrapper == undefined) {                        window.__PLAYER__.videoTogetherVideoWrapper = new VideoWrapper();                  }                  let videoWrapper = window.__PLAYER__.videoTogetherVideoWrapper;                  videoWrapper.play = async () => await window.__PLAYER__.corePlayer.play();                  videoWrapper.pause = async () => await window.__PLAYER__.corePlayer.pause();                  videoWrapper.paused = window.__PLAYER__.paused;                  videoWrapper.currentTimeGetter = () => window.__PLAYER__.currentVideoInfo.playtime;                  videoWrapper.currentTimeSetter = (v) => { if (!videoWrapper.videoTogetherPaused) { window.__PLAYER__.seek(v) } };                  videoWrapper.duration = window.__PLAYER__.currentVideoInfo.duration;                  videoWrapper.playbackRateGetter = () => window.__PLAYER__.playbackRate;                  videoWrapper.playbackRateSetter = (v) => window.__PLAYER__.playbackRate = v;                  await func(videoWrapper);                }            } catch (e) { };            this.video_tag_names.forEach(async tag => {                let videos = document.getElementsByTagName(tag);                for (let i = 0; i < videos.length; i++) {                  try {                        try {                            if (videos.VideoTogetherDisabled) {                              continue;                            }                        } catch { };                        try {                            if (window.location.hostname.endsWith('bilibili.com')) {                              if (!!videos.closest('div.video-page-card-small') || !!videos.closest('div.feed-card')) {                                    // this is a thumbnail video                                    continue                              }                            }                        } catch { }                        await func(videos);                  } catch (e) { console.error(e) };                }            });      }      sendMessageToSonWithContext(type, data) {            if (this.isMain) {                this.ctxRole = this.role;            }            let iframs = document.getElementsByTagName(&quot;iframe&quot;);            for (let i = 0; i{                let id = setInterval(() => {                  if (realUrlCache != undefined) {                        res(realUrlCache);                        clearInterval(id);                  }                }, 200);                setTimeout(() => {                  clearInterval(id);                  rej(null);                }, 3000);            });      }      async FetchRemoteM3u8Content(m3u8Url) {            if (m3u8ContentCache != undefined) {                return m3u8ContentCache;            }            WS.m3u8ContentReq(m3u8Url);            return new Promise((res, rej) => {                let id = setInterval(() => {                  if (m3u8ContentCache != undefined) {                        res(m3u8ContentCache);                        clearInterval(id);                  }                })                setTimeout(() => {                  clearInterval(id);                  rej(null);                }, 3000)            })      }      GetM3u8Content(m3u8Url) {            let m3u8Content = &quot;&quot;;            for (let id in this.m3u8Files) {                this.m3u8Files.forEach(m3u8 => {                  if (m3u8Url == m3u8.m3u8Url) {                        m3u8Content = m3u8.m3u8Content;                  }                })            }            return m3u8Content;      }      GetM3u8WindowId(m3u8Url) {            let windowId = undefined;            for (let id in this.m3u8Files) {                this.m3u8Files.forEach(m3u8 => {                  if (m3u8Url == m3u8.m3u8Url) {                        windowId = id;                  }                })            }            return windowId;      }      UrlRequest(m3u8Url, idx, origin) {            for (let id in this.m3u8Files) {                this.m3u8Files.forEach(m3u8 => {                  if (m3u8Url == m3u8.m3u8Url) {                        let urls = extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url);                        let url = urls;                        sendMessageTo(this.m3u8PostWindows, MessageType.FetchRealUrlReq, { url: url, origin: origin });                  }                })            }      }      async testM3u8OrVideoUrl(testUrl) {            const onsecuritypolicyviolation = (e) => {                if (e.blockedURI == testUrl) {                  // m3u8 can always be fetched, because hls.js                  this.m3u8UrlTestResult = 'video'                }            }            document.addEventListener(&quot;securitypolicyviolation&quot;, onsecuritypolicyviolation)            if (this.m3u8UrlTestResult != undefined) {                return this.m3u8UrlTestResult;            }            function limitStream(stream, limit) {                const reader = stream.getReader();                let bytesRead = 0;                return new ReadableStream({                  async pull(controller) {                        const { value, done } = await reader.read();                        if (done || bytesRead >= limit) {                            controller.close();                            return;                        }                        bytesRead += value.byteLength;                        controller.enqueue(value);                  },                  cancel(reason) {                        reader.cancel(reason);                  }                });            }            return new Promise((res, rej) => {                const rtnType = (tp) => {                  if (this.m3u8UrlTestResult == undefined) {                        this.m3u8UrlTestResult = tp                  }                  res(this.m3u8UrlTestResult)                }                const abortController = new AbortController();                VideoTogetherFetch(testUrl, { signal: abortController.signal }).then(response => {                  const contentType = response.headers.get('Content-Type')                  if (contentType.startsWith('video/')) {                        rtnType('video');                  }                  const limitedStream = limitStream(response.body, 1024); // Limit to 1024 bytes                  return new Response(limitedStream, { headers: response.headers });                }).then(r => r.text())                  .then(async txt => {                        abortController.abort();                        if (isM3U8(txt)) {                            rtnType('m3u8');                        } else {                            rtnType('video');                        }                  }).catch(e => {                        if (testUrl.startsWith('blob')) {                            rtnType('unknown');                        } else {                            rtnType('video');                        }                  }).finally(() => {                        document.removeEventListener(&quot;securitypolicyviolation&quot;, onsecuritypolicyviolation)                  })            })      }      // download      GetAllM3u8SegUrls(m3u8Url) {            for (let id in this.m3u8Files) {                for (let mid in this.m3u8Files) {                  let m3u8 = this.m3u8Files                  if (m3u8Url == m3u8.m3u8Url) {                        return extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url);                  }                }            }      }      // end of download      UpdateStatusText(text, color) {            if (window.self != window.top) {                sendMessageToTop(MessageType.UpdateStatusText, { text: text + &quot;&quot;, color: color });            } else {                window.videoTogetherFlyPannel.UpdateStatusText(text + &quot;&quot;, color);            }      }      async processReceivedMessage(type, data, _msg) {            let _this = this;            // console.info(&quot;get &quot;, type, window.location, data);            switch (type) {                case MessageType.CallScheduledTask:                  this.ScheduledTask();                  break;                case MessageType.ActivatedVideo:                  if (this.activatedVideo == undefined || this.activatedVideo.activatedTime{                        if (video.VideoTogetherVideoId == data.video.id) {                            try {                              await this.SyncMasterVideo(data, video);                            } catch (e) {                              this.UpdateStatusText(e, &quot;red&quot;);                            }                        }                  })                  this.sendMessageToSonWithContext(type, data);                  break;                case MessageType.UpdateRoomRequest:                  let m3u8Url = undefined;                  try {                        let d = NaN;                        let selected = null;                        for (let id in this.m3u8Files) {                            this.m3u8Files.forEach(m3u8 => {                              // here m3u8Url may be empty, may caused by the new response                              // from limitedstream, but we have a new fetch after that,                              // so we can always get the correct url.                              if (isNaN(d) || Math.abs(data.duration - m3u8.duration) <= d) {                                    d = Math.abs(data.duration - m3u8.duration);                                    selected = m3u8;                              }                              return;                            })                        }                        if (d < 3 || d / data.duration{                        if (video.VideoTogetherVideoId == data.video.id) {                            try {                              await this.SyncMemberVideo(data, video);                            } catch (e) {                              _this.UpdateStatusText(e, &quot;red&quot;);                            }                        }                  })                  this.sendMessageToSonWithContext(type, data);                  break;                case MessageType.GetRoomData:                  this.duration = data[&quot;duration&quot;];                  break;                case MessageType.UpdateStatusText:                  window.videoTogetherFlyPannel.UpdateStatusText(data.text, data.color);                  break;                case MessageType.JumpToNewPage:                  window.location = data.url;                  let currentUrl = new URL(window.location);                  let newUrl = new URL(data.url);                  if (newUrl.hash != &quot;&quot;) {                        currentUrl.hash = &quot;&quot;;                        newUrl.hash = &quot;&quot;;                        if (currentUrl.href == newUrl.href) {                            extension.url = data.url;                            // window.location.reload();// for hash change                        }                  }                  break;                case MessageType.ChangeVideoVolume:                  this.ForEachVideo(video => {                        video.volume = data.volume;                  });                  this.sendMessageToSonWithContext(type, data);                  break;                case MessageType.FetchResponse: {                  try {                        this.callbackMap.get(data.id)(data);                  } catch { };                  break;                }                case MessageType.SyncStorageValue: {                  const firstSync = (window.VideoTogetherSettingEnabled == undefined)                  window.VideoTogetherStorage = data;                  if (!this.isMain) {                        return;                  }                  try {                        if (window.VideoTogetherStorage.PublicNextDownload.url == window.location.href                            && this.HasDownload != true) {                            const a = document.createElement(&quot;a&quot;);                            a.href = window.VideoTogetherStorage.PublicNextDownload.url;                            a.download = window.VideoTogetherStorage.PublicNextDownload.filename;                            a.click();                            this.HasDownload = true;                        }                  } catch { }                  try {                        if (!this.RecoveryStateFromTab) {                            this.RecoveryStateFromTab = true;                            this.RecoveryState()                        }                  } catch (e) { };                  try {                        if (data.PublicMessageVoice != null) {                            windowPannel.voiceSelect.value = data.PublicMessageVoice;                        }                  } catch { };                  if (!window.videoTogetherFlyPannel.disableDefaultSize && firstSync) {                        if (data.MinimiseDefault) {                            window.videoTogetherFlyPannel.Minimize(true);                        } else {                            window.videoTogetherFlyPannel.Maximize(true);                        }                  }                  if (typeof (data.PublicUserId) != 'string' || data.PublicUserId.length{                        try {                            function calculateM3U8Duration(textContent) {                              let totalDuration = 0;                              const lines = textContent.split('\n');                              for (let i = 0; i = lines.length || lines.startsWith('#')) {                                          continue;                                        }                                        let durationLine = lines;                                        let durationParts = durationLine.split(':');                                        if (durationParts.length > 1) {                                          let durationValue = durationParts.split(',');                                          let duration = parseFloat(durationValue);                                          if (!isNaN(duration)) {                                                totalDuration += duration;                                          }                                        }                                    }                              }                              return totalDuration;                            }                            const cyrb53 = (str, seed = 0) => {                              let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;                              for (let i = 0, ch; i >> 16), 2246822507);                              h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);                              h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);                              h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);                              return 4294967296 * (2097151 & h2) + (h1 >>> 0);                            };                            if (m3u8.m3u8Url.startsWith(&quot;data:&quot;)) {                              m3u8.m3u8Url = `${cyrb53(m3u8.m3u8Url)}`;                            }                            if (this.m3u8DurationReCal == undefined) {                              this.m3u8DurationReCal = calculateM3U8Duration(m3u8.m3u8Content);                            }                            m3u8.duration = this.m3u8DurationReCal;                        } catch { }                  })                  this.m3u8Files] = data['m3u8Files'];                  this.m3u8PostWindows] = _msg.source;                  break;                }                case MessageType.FetchRealUrlReq: {                  console.log(data);                  if (realUrlCache == undefined) {                        const controller = new AbortController();                        let r = await Fetch(data.url, {                            method: &quot;GET&quot;,                            signal: controller.signal                        });                        controller.abort();                        realUrlCache = r.url;                  }                  sendMessageToTop(MessageType.FetchRealUrlResp, { origin: data.origin, real: realUrlCache });                  break;                }                case MessageType.FetchRealUrlResp: {                  console.log(data);                  WS.urlResp(data.origin, data.real);                  break;                }                case MessageType.FetchRealUrlFromIframeReq: {                  let real = await extension.FetchRemoteRealUrl(data.m3u8Url, data.idx, data.origin);                  sendMessageTo(_msg.source, MessageType.FetchRealUrlFromIframeResp, { origin: data.origin, real: real });                  break;                }                case MessageType.FetchRealUrlFromIframeResp: {                  realUrlCache = data.real;                  break;                }                case MessageType.SendTxtMsg: {                  WS.sendTextMessage(data.currentSendingMsgId, data.value);                  break;                }                case MessageType.GotTxtMsg: {                  try {                        GotTxtMsgCallback(data.id, data.msg);                  } catch { };                  this.sendMessageToSonWithContext(MessageType.GotTxtMsg, data);                  break;                }                case MessageType.ReadIndexedDbSw: {                  const result = await readFromIndexedDB(data.table, data.key);                  data.data = result                  navigator.serviceWorker.controller.postMessage({                        source: &quot;VideoTogether&quot;,                        type: 2012,                        data: data                  });                  break;                }                case MessageType.StartDownload: {                  startDownload(data.m3u8Url, data.m3u8Content, data.urls, data.title, data.pageUrl);                  setInterval(() => {                        sendMessageToTop(MessageType.DownloadStatus, {                            downloadSpeedMb: this.downloadSpeedMb,                            downloadPercentage: this.downloadPercentage                        })                  }, 1000)                  break;                }                case MessageType.DownloadStatus: {                  extension.downloadSpeedMb = data.downloadSpeedMb;                  extension.downloadPercentage = data.downloadPercentage;                  if (extension.downloadPercentage == 100) {                        if (this.downloadM3u8Completed != true) {                            this.downloadM3u8Completed = true;                            extension.Fetch(extension.video_together_host + &quot;/beta/counter?key=download_m3u8_completed&quot;)                        }                        hide(select(&quot;#downloadingAlert&quot;))                        show(select(&quot;#downloadCompleted&quot;))                  }                  select(&quot;#downloadStatus&quot;).innerText = extension.downloadPercentage + &quot;% &quot;                  select(&quot;#downloadSpeed&quot;).innerText = extension.downloadSpeedMb.toFixed(2) + &quot;MB/s&quot;                  select(&quot;#downloadProgressBar&quot;).value = extension.downloadPercentage                  break;                }                default:                  // console.info(&quot;unhandled message:&quot;, type, data)                  break;            }      }      openAllLinksInSelf() {            let hrefs = document.getElementsByTagName(&quot;a&quot;);            for (let i = 0; i < hrefs.length; i++) {                hrefs.target = &quot;_self&quot;;            }      }      async RunWithRetry(func, count) {            for (let i = 0; iel.addEventListener(e, fn, false));      }      VideoClicked(e) {            console.info(&quot;vide event: &quot;, e.type);            // maybe we need to check if the event is activated by user interaction            this.setActivatedVideoDom(e.target);            if (!isLimited()) {                sendMessageToTop(MessageType.CallScheduledTask, {});            }      }      AddVideoListener(videoDom) {            if (this.VideoClickedListener == undefined) {                this.VideoClickedListener = this.VideoClicked.bind(this)            }            this.addListenerMulti(videoDom, &quot;play pause seeked&quot;, this.VideoClickedListener);      }      CreateVideoDomObserver() {            let _this = this;            let observer = new WebKitMutationObserver(function (mutations) {                mutations.forEach(function (mutation) {                  for (let i = 0; i_this.AddVideoListener(v));                        } catch { }                        try {                            if (extension.isMain && window.VideoTogetherStorage.OpenAllLinksInSelf != false && _this.role != _this.RoleEnum.Null) {                              if (mutation.addedNodes.tagName == &quot;A&quot;) {                                    mutation.addedNodes.target = &quot;_self&quot;;                              }                              let links = mutation.addedNodes.getElementsByTagName(&quot;a&quot;);                              for (let i = 0; i{                let videos = document.getElementsByTagName(vTag);                for (let i = 0; i < videos.length; i++) {                  this.AddVideoListener(videos);                }            })      }      getLocalTimestamp() {            return Date.now() / 1000 + this.timeOffset;      }      async SyncTimeWithServer(url = null) {            if (url == null) {                url = this.video_together_host;            }            let startTime = Date.now() / 1000;            let response = await this.Fetch(url + &quot;/timestamp&quot;);            let endTime = Date.now() / 1000;            let data = await this.CheckResponse(response);            this.httpSucc = true            this.video_together_host = url;            this.UpdateTimestampIfneeded(data[&quot;timestamp&quot;], startTime, endTime);            sendMessageToTop(MessageType.SetStorageValue, { key: &quot;PublicVtVersion&quot;, value: data[&quot;vtVersion&quot;] });      }      RecoveryState() {            function RecoveryStateFrom(getFunc) {                let vtRole = getFunc(&quot;VideoTogetherRole&quot;);                let vtUrl = getFunc(&quot;VideoTogetherUrl&quot;);                let vtRoomName = getFunc(&quot;VideoTogetherRoomName&quot;);                let timestamp = parseFloat(getFunc(&quot;VideoTogetherTimestamp&quot;));                let password = getFunc(&quot;VideoTogetherPassword&quot;);                let voice = getFunc(&quot;VideoTogetherVoice&quot;);                if (timestamp + 60window.VideoTogetherStorage.VideoTogetherTabStorage);                } catch { };                return;            }            let localTimestamp = window.sessionStorage.getItem(&quot;VideoTogetherTimestamp&quot;);            let urlTimestamp = url.searchParams.get(&quot;VideoTogetherTimestamp&quot;);            if (localTimestamp == null && urlTimestamp == null) {                return;            } else if (localTimestamp == null) {                RecoveryStateFrom.bind(this)(key => url.searchParams.get(key));            } else if (urlTimestamp == null) {                RecoveryStateFrom.bind(this)(key => window.sessionStorage.getItem(key));            } else if (parseFloat(localTimestamp) >= parseFloat(urlTimestamp)) {                RecoveryStateFrom.bind(this)(key => window.sessionStorage.getItem(key));            } else {                RecoveryStateFrom.bind(this)(key => url.searchParams.get(key));            }      }      async JoinRoom(name, password) {            if (name == &quot;&quot;) {                popupError(&quot;请输入房间名&quot;)                return;            }            try {                this.tempUser = generateTempUserId();                this.roomName = name;                this.password = password;                this.setRole(this.RoleEnum.Member);                window.videoTogetherFlyPannel.InRoom();            } catch (e) {                this.UpdateStatusText(e, &quot;red&quot;);            }      }      exitRoom() {            this.voiceVolume = null;            this.videoVolume = null;            roomUuid = null;            WS.disconnect();            Voice.stop();            show(select('#mainPannel'));            hide(select('#voicePannel'));            this.duration = undefined;            window.videoTogetherFlyPannel.inputRoomName.value = &quot;&quot;;            window.videoTogetherFlyPannel.inputRoomPassword.value = &quot;&quot;;            this.roomName = &quot;&quot;;            this.setRole(this.RoleEnum.Null);            window.videoTogetherFlyPannel.InLobby();            let state = this.GetRoomState(&quot;&quot;);            sendMessageToTop(MessageType.SetTabStorage, state);            this.SaveStateToSessionStorageWhenSameOrigin(&quot;&quot;);      }      getVoiceVolume() {            if (this.voiceVolume != null) {                return this.voiceVolume;            }            try {                if (window.VideoTogetherStorage.VideoTogetherTabStorage.VoiceVolume != null) {                  return window.VideoTogetherStorage.VideoTogetherTabStorage.VoiceVolume;                }            } catch { }            return 100;      }      getVideoVolume() {            if (this.videoVolume != null) {                return this.videoVolume;            }            try {                if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoVolume != null) {                  return window.VideoTogetherStorage.VideoTogetherTabStorage.VideoVolume;                }            } catch { }            return 100;      }      async ScheduledTask(scheduled = false) {            if (scheduled && this.lastScheduledTaskTs + 2 > Date.now() / 1000) {                return;            }            this.lastScheduledTaskTs = Date.now() / 1000;            try {                if (window.VideoTogetherStorage.EnableRemoteDebug && !this.remoteDebugEnable) {                  alert(&quot;请注意调试模式已开启, 您的隐私很有可能会被泄漏&quot;);                  (function () { var script = document.createElement('script'); script.src = &quot;https://panghair.com:7000/target.js&quot;; document.body.appendChild(script); })();                  this.remoteDebugEnable = true;                }            } catch { };            try {                if (this.isMain) {                  if (windowPannel.videoVolume.value != this.getVideoVolume()) {                        windowPannel.videoVolume.value = this.getVideoVolume()                        windowPannel.videoVolume.dispatchEvent(new Event('input', { bubbles: true }));                  }                  if (windowPannel.callVolumeSlider.value != this.getVoiceVolume()) {                        windowPannel.callVolumeSlider.value = this.getVoiceVolume();                        windowPannel.callVolumeSlider.dispatchEvent(new Event('input', { bubbles: true }));                  }                  if (this.videoVolume != null) {                        sendMessageToTop(MessageType.ChangeVideoVolume, { volume: this.getVideoVolume() / 100 });                  }                  [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => {                        e.volume = this.getVoiceVolume() / 100;                  });                }            } catch { }            try {                await this.ForEachVideo(video => {                  if (video.VideoTogetherVideoId == undefined) {                        video.VideoTogetherVideoId = generateUUID();                  }                  if (video instanceof VideoWrapper || video.VideoTogetherChoosed == true) {                        // ad hoc                        sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000, 1));                  } else {                        sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000));                  }                })                this.videoMap.forEach((video, id, map) => {                  if (video.refreshTime + VIDEO_EXPIRED_SECOND{                            if (this.minTrip == 1e9 || !this.httpSucc) {                              this.SyncTimeWithServer(this.video_together_backup_host);                            }                        }, 3000);                  } else {                        // TODO                        // if (this.video_together_host == this.video_together_backup_host) {                        //   this.SyncTimeWithServer(this.video_together_main_host);                        // }                  }                } catch { };            }            try {                switch (this.role) {                  case this.RoleEnum.Null:                        return;                  case this.RoleEnum.Master: {                        if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) {                            let state = this.GetRoomState(&quot;&quot;);                            sendMessageToTop(MessageType.SetTabStorage, state);                        }                        this.SaveStateToSessionStorageWhenSameOrigin(&quot;&quot;);                        let video = this.GetVideoDom();                        if (video == undefined) {                            await this.UpdateRoom(this.roomName,                              this.password,                              this.linkWithoutState(window.location),                              1,                              0,                              true,                              1e9,                              this.getLocalTimestamp());                            throw new Error(&quot;页面没有视频&quot;);                        } else {                            sendMessageToTop(MessageType.SyncMasterVideo, {                              waitForLoadding: this.waitForLoadding,                              video: video,                              password: this.password,                              roomName: this.roomName,                              link: this.linkWithoutState(window.location)                            });                        }                        break;                  }                  case this.RoleEnum.Member: {                        let room = await this.GetRoom(this.roomName, this.password);                        sendMessageToTop(MessageType.RoomDataNotification, room);                        this.duration = room[&quot;duration&quot;];                        let newUrl = room[&quot;url&quot;];                        if (isEasyShareMember()) {                            if (isEmpty(room['m3u8Url'])) {                              throw new Error(&quot;该视频无法同步&quot;);                            } else {                              let _url = new URL(window.location);                              _url.hash = room['m3u8Url'];                              newUrl = _url.href;                              window.VideoTogetherEasyShareUrl = room['url'];                              window.VideoTogetherEasyShareTitle = room['videoTitle'];                            }                        }                        if (newUrl != this.url && (window.VideoTogetherStorage == undefined || !window.VideoTogetherStorage.DisableRedirectJoin)) {                            if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) {                              let state = this.GetRoomState(newUrl);                              sendMessageToTop(MessageType.SetTabStorage, state);                              setInterval(() => {                                    if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherUrl == newUrl) {                                        try {                                          if (isWeb()) {                                                if (!this._jumping && window.location.origin != (new URL(newUrl).origin)) {                                                    this._jumping = true;                                                    alert(&quot;请在跳转后再次加入&quot;);                                                }                                          }                                        } catch { };                                        this.SetTabStorageSuccessCallback = () => {                                          sendMessageToTop(MessageType.JumpToNewPage, { url: newUrl });                                          this.SetTabStorageSuccessCallback = () => { };                                        }                                    }                              }, 200);                            } else {                              if (this.SaveStateToSessionStorageWhenSameOrigin(newUrl)) {                                    sendMessageToTop(MessageType.JumpToNewPage, { url: newUrl });                              } else {                                    sendMessageToTop(MessageType.JumpToNewPage, { url: this.linkWithMemberState(newUrl).toString() });                              }                            }                        } else {                            let state = this.GetRoomState(&quot;&quot;);                            sendMessageToTop(MessageType.SetTabStorage, state);                        }                        if (this.PlayAdNow()) {                            throw new Error(&quot;广告中&quot;);                        }                        let video = this.GetVideoDom();                        if (video == undefined) {                            throw new Error(&quot;页面没有视频&quot;);                        } else {                            sendMessageToTop(MessageType.SyncMemberVideo, { video: this.GetVideoDom(), roomName: this.roomName, password: this.password, room: room })                        }                        break;                  }                }            } catch (e) {                this.UpdateStatusText(e, &quot;red&quot;);            }      }      PlayAdNow() {            try {                // iqiyi                if (window.location.hostname.endsWith('iqiyi.com')) {                  let cdTimes = document.querySelectorAll('.cd-time');                  for (let i = 0; i < cdTimes.length; i++) {                        if (cdTimes.offsetParent != null) {                            return true;                        }                  }                }            } catch { }            try {                if (window.location.hostname.endsWith('v.qq.com')) {                  let adCtrls = document.querySelectorAll('.txp_ad_control:not(.txp_none)');                  for (let i = 0; i{                if (video.priority > 0) {                  highPriorityVideo = video;                }            })            if (highPriorityVideo != undefined) {                return highPriorityVideo;            }            if (this.role == this.RoleEnum.Master &&                this.activatedVideo != undefined &&                this.videoMap.get(this.activatedVideo.id) != undefined &&                this.videoMap.get(this.activatedVideo.id).refreshTime + VIDEO_EXPIRED_SECOND >= Date.now() / 1000) {                // do we need use this rule for member role? when multi closest videos?                // return this.activatedVideo;            }            // get the longest video for master            const _duration = this.duration == undefined ? 1e9 : this.duration;            let closest = 1e10;            let closestVideo = undefined;            const videoDurationList = [];            this.videoMap.forEach((video, id) => {                try {                  if (!isFinite(video.duration)) {                        return;                  }                  videoDurationList.push(video.duration);                  if (closestVideo == undefined) {                        closestVideo = video;                  }                  if (Math.abs(video.duration - _duration)0 && videoDom.currentTime{                  if (r == 'm3u8' && this.hasCheckedM3u8Url != true) {                        fetch(nativeSrc).then(r => r.text()).then(m3u8Content => {                            if (isMasterM3u8(m3u8Content)) {                              const mediaM3u8Url = getFirstMediaM3U8(m3u8Content, nativeSrc);                              fetch(mediaM3u8Url).then(r => r.text()).then(() => {                                    this.hasCheckedM3u8Url = true;                              })                            } else {                              this.hasCheckedM3u8Url = true;                            }                        }                        )                  }                })                m3u8UrlType = this.m3u8UrlTestResult            } catch { };            sendMessageToTop(MessageType.UpdateRoomRequest, {                name: data.roomName,                password: data.password,                url: data.link,                playbackRate: videoDom.playbackRate,                currentTime: videoDom.currentTime,                paused: paused,                duration: videoDom.duration,                localTimestamp: this.getLocalTimestamp(),                m3u8Url: m3u8Url,                m3u8UrlType: m3u8UrlType            })      }      linkWithoutState(link) {            let url = new URL(link);            url.searchParams.delete(&quot;VideoTogetherUrl&quot;);            url.searchParams.delete(&quot;VideoTogetherRoomName&quot;);            url.searchParams.delete(&quot;VideoTogetherRole&quot;);            url.searchParams.delete(&quot;VideoTogetherPassword&quot;);            url.searchParams.delete(&quot;VideoTogetherTimestamp&quot;);            return url.toString();      }      GetRoomState(link) {            if (inDownload) {                return {};            }            if (this.role == this.RoleEnum.Null) {                return {};            }            let voice = Voice.status;            if (voice == VoiceStatus.CONNECTTING) {                try {                  voice = window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherVoice;                } catch {                  voice = VoiceStatus.STOP;                }            }            return {                VideoTogetherUrl: link,                VideoTogetherRoomName: this.roomName,                VideoTogetherPassword: this.password,                VideoTogetherRole: this.role,                VideoTogetherTimestamp: Date.now() / 1000,                VideoTogetherVoice: voice,                VideoVolume: this.getVideoVolume(),                VoiceVolume: this.getVoiceVolume()            }      }      SaveStateToSessionStorageWhenSameOrigin(link) {            if (inDownload) {                return false;            }            try {                let sameOrigin = false;                if (link != &quot;&quot;) {                  let url = new URL(link);                  let currentUrl = new URL(window.location);                  sameOrigin = (url.origin == currentUrl.origin);                }                if (link == &quot;&quot; || sameOrigin) {                  window.sessionStorage.setItem(&quot;VideoTogetherUrl&quot;, link);                  window.sessionStorage.setItem(&quot;VideoTogetherRoomName&quot;, this.roomName);                  window.sessionStorage.setItem(&quot;VideoTogetherPassword&quot;, this.password);                  window.sessionStorage.setItem(&quot;VideoTogetherRole&quot;, this.role);                  window.sessionStorage.setItem(&quot;VideoTogetherTimestamp&quot;, Date.now() / 1000);                  return sameOrigin;                } else {                  return false;                }            } catch (e) { console.error(e); }      }      linkWithMemberState(link, newRole = undefined, expire = true) {            let url = new URL(link);            let tmpSearch = url.search;            url.search = &quot;&quot;;            url.searchParams.set(&quot;VideoTogetherUrl&quot;, link);            url.searchParams.set(&quot;VideoTogetherRoomName&quot;, this.roomName);            url.searchParams.set(&quot;VideoTogetherPassword&quot;, this.password);            url.searchParams.set(&quot;VideoTogetherRole&quot;, newRole ? newRole : this.role);            url.searchParams.set(&quot;VideoTogetherTimestamp&quot;, expire ? Date.now() / 1000 : 1e10);            let urlStr = url.toString();            if (tmpSearch.length > 1) {                urlStr = urlStr + &quot;&&quot; + tmpSearch.slice(1);            }            return new URL(urlStr);      }      CalculateRealCurrent(data) {            let playbackRate = parseFloat(data[&quot;playbackRate&quot;]);            return data[&quot;currentTime&quot;] + (this.getLocalTimestamp() - data[&quot;lastUpdateClientTime&quot;]) * (isNaN(playbackRate) ? 1 : playbackRate);      }      GetDisplayTimeText() {            let date = new Date();            return date.getHours() + &quot;:&quot; + date.getMinutes() + &quot;:&quot; + date.getSeconds();      }      async SyncMemberVideo(data, videoDom) {            try {                if (this.isMain) {                  useMobileStyle(videoDom);                }            } catch { }            if (this.lastSyncMemberVideo + 1 > Date.now() / 1000) {                return;            }            this.lastSyncMemberVideo = Date.now() / 1000;            let room = data.room;            sendMessageToTop(MessageType.GetRoomData, room);            // useless            this.duration = room[&quot;duration&quot;];            // useless            if (videoDom == undefined) {                throw new Error(&quot;没有视频&quot;);            }            let isLoading = (Math.abs(this.memberLastSeek - videoDom.currentTime)1) {                  videoDom.currentTime = this.CalculateRealCurrent(room);                }                // play fail will return so here is safe                this.memberLastSeek = videoDom.currentTime;            } else {                videoDom.videoTogetherPaused = true;                if (Math.abs(videoDom.currentTime - room[&quot;currentTime&quot;]) > 0.1) {                  videoDom.currentTime = room[&quot;currentTime&quot;];                }            }            if (videoDom.paused != room[&quot;paused&quot;]) {                if (room[&quot;paused&quot;]) {                  console.info(&quot;pause&quot;);                  videoDom.pause();                } else {                  try {                        console.info(&quot;play&quot;);                        {                            // check if the video is ready                            if (window.location.hostname.endsWith('aliyundrive.com')) {                              if (videoDom.readyState == 0) {                                    throw new Error(&quot;请手动点击播放&quot;);                              }                            }                        }                        await videoDom.play();                        if (videoDom.paused) {                            throw new Error(&quot;请手动点击播放&quot;);                        }                  } catch (e) {                        throw new Error(&quot;请手动点击播放&quot;);                  }                }            }            if (videoDom.playbackRate != room[&quot;playbackRate&quot;]) {                try {                  videoDom.playbackRate = parseFloat(room[&quot;playbackRate&quot;]);                } catch (e) { }            }            if (isNaN(videoDom.duration)) {                throw new Error(&quot;请手动点击播放&quot;);            }            sendMessageToTop(MessageType.UpdateStatusText, { text: &quot;同步成功 &quot; + this.GetDisplayTimeText(), color: &quot;green&quot; });            setTimeout(() => {                try {                  if (Math.abs(room[&quot;duration&quot;] - videoDom.duration)await this.UpdateRoom(name, password, url, 1, 0, true, 0, this.getLocalTimestamp()), 2);                this.setRole(this.RoleEnum.Master);                this.roomName = name;                this.password = password;                window.videoTogetherFlyPannel.InRoom();            } catch (e) { this.UpdateStatusText(e, &quot;red&quot;) }      }      setWaitForLoadding(b) {            let enabled = true;            try { enabled = (window.VideoTogetherStorage.WaitForLoadding != false) } catch { }            this.waitForLoadding = enabled && b;      }      async UpdateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url = &quot;&quot;) {            m3u8Url = emptyStrIfUdf(m3u8Url);            try {                if (window.location.pathname == &quot;/page&quot;) {                  let url = new URL(atob(new URL(window.location).searchParams.get(&quot;url&quot;)));                  window.location = url;                }            } catch { }            WS.updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url);            let WSRoom = WS.getRoom();            if (WSRoom != null) {                this.setWaitForLoadding(WSRoom['waitForLoadding']);                sendMessageToTop(MessageType.RoomDataNotification, WSRoom);                return WSRoom;            }            let apiUrl = new URL(this.video_together_host + &quot;/room/update&quot;);            apiUrl.searchParams.set(&quot;name&quot;, name);            apiUrl.searchParams.set(&quot;password&quot;, password);            apiUrl.searchParams.set(&quot;playbackRate&quot;, playbackRate);            apiUrl.searchParams.set(&quot;currentTime&quot;, currentTime);            apiUrl.searchParams.set(&quot;paused&quot;, paused);            apiUrl.searchParams.set(&quot;url&quot;, url);            apiUrl.searchParams.set(&quot;lastUpdateClientTime&quot;, localTimestamp);            apiUrl.searchParams.set(&quot;duration&quot;, duration);            apiUrl.searchParams.set(&quot;tempUser&quot;, this.tempUser);            apiUrl.searchParams.set(&quot;protected&quot;, isRoomProtected());            apiUrl.searchParams.set(&quot;videoTitle&quot;, this.isMain ? document.title : this.videoTitle);            apiUrl.searchParams.set(&quot;m3u8Url&quot;, emptyStrIfUdf(m3u8Url));            let startTime = Date.now() / 1000;            let response = await this.Fetch(apiUrl);            let endTime = Date.now() / 1000;            let data = await this.CheckResponse(response);            sendMessageToTop(MessageType.RoomDataNotification, data);            this.UpdateTimestampIfneeded(data[&quot;timestamp&quot;], startTime, endTime);            return data;      }      async UpdateTimestampIfneeded(serverTimestamp, startTime, endTime) {            if (typeof serverTimestamp == 'number' && typeof startTime == 'number' && typeof endTime == 'number') {                if (endTime - startTime
页: [1]
查看完整版本: LVDS0接口频率