《我的世界》公益生存来啦!大型整合包探索之旅!

Remnant公益服务器

欢迎来捏,这里是很多大型整合包组成的服务器

在这里你可以和朋友讨论让服务器开你想玩的整合包或者是E6E

服务器QQ群:664315356

服务器实机风景图

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

#videoTogetherLoading {
touch-action: none;
height: 50px;
border: 1px solid #c9c8c8;
background: #ffffff;
color: #212529;
display: flex;
align-items: center;
z-index: 2147483646;
position: fixed;
bottom: 15px;
right: 15px;
width: 250px;
text-align: center;
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
border-radius: 5px;
}
#videoTogetherLoadingwrap {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#videoTogetherLoadingwrap img {
margin-right: 12px;
}
#videoTogetherLoadingwrap a {
color: #212529;
text-decoration: none;
}
#videoTogetherLoadingwrap a:hover {
color: #1890ff;
text-decoration: underline;
}

{
if (e.data.source == vtMsgSrc) {
switch (e.data.type) {
case 36: {
this._url = e.data.data.url;
this._title = e.data.data.title;
this._isEasySharePage = e.data.data.isEasySharePage;
if (this._initCallback != undefined) {
this._initCallback();
this._initCallback = undefined;
}
break;
}
}
}
})
}
async asyncInit() {
if (this.url != undefined) {
return;
}
return new Promise((res, rej) => {
if (this._initCallback != undefined) {
rej("init callback is already set")
}
this._initCallback = res;
})
}
get url() {
if (!isWrapperFrame) {
return window.location.href;
}
return this._url;
}
get title() {
if (!isWrapperFrame) {
return document.title;
}
return this._title;
}
get isEasySharePage() {
if (!isWrapperFrame) {
return window.VideoTogetherEasyShareMemberSite;
}
return this._isEasySharePage;
}
}

const topFrameState = new TopFrameState();
const mouseMoveEvent = [‘mousemove’, ‘touchmove’, ‘pointermove’];
const mouseUpEvent = [‘mouseup’, ‘touchend’, ‘pointerup’];

const SelfWrapperState = {
positionX: 0,
positionY: 0,
sizeX: 0,
sizeY: 0,
movingOffsetX: 0,
movingOffsetY: 0,
}
const WrapperIframeSource = ‘VT_WrapperIframe’;
class WrapperIframe {
constructor() {
this.frame = document.createElement(‘iframe’);
this.frame.src = ‘https://2gether.video/videotogether_wrapper.html’;
this.frame.allow = ‘microphone;’;
this.frame.style = ‘position: absolute; right: 0px; bottom: 0px; width: 262px; height: 212px; background: transparent; border: none; z-index: 2147483647; position:fixed;’;
(document.body || document.documentElement).appendChild(this.frame);
window.addEventListener(‘message’, (e) => {
if (e.data.source == WrapperIframeSource) {
switch (e.data.type) {
case ‘moving’:
this._move(e.data.data.x, e.data.data.y);
break;
case ‘init’:
this._notifyState();
break;
}
}
})
window.addEventListener(‘resize’, (e) => {
const left = window.getComputedStyle(this.frame).getPropertyValue(‘left’).split(‘px’)[0] * 1;
const top = window.getComputedStyle(this.frame).getPropertyValue(‘top’).split(‘px’)[0] * 1;
this._move(left, top)
})
}
_notifyState() {
const left = window.getComputedStyle(this.frame).getPropertyValue(‘left’).split(‘px’)[0] * 1;
const top = window.getComputedStyle(this.frame).getPropertyValue(‘top’).split(‘px’)[0] * 1;
const width = window.getComputedStyle(this.frame).getPropertyValue(‘width’).split(‘px’)[0] * 1;
const height = window.getComputedStyle(this.frame).getPropertyValue(‘height’).split(‘px’)[0] * 1;
PostMessage(this.frame.contentWindow, {
source: WrapperIframeSource,
type: ‘state’,
data: {
left: left,
top: top,
width: width,
height: height
}
});
}

_move(screenX, screenY) {
screenX = Math.max(0, Math.min(screenX, window.innerWidth – this.frame.offsetWidth));
screenY = Math.max(0, Math.min(screenY, window.innerHeight – this.frame.offsetHeight));
this.frame.style.left = `${screenX}px`;
this.frame.style.top = `${screenY}px`;
this._notifyState();
}
static Moving(e) {
let targetX;
let targetY;

if (e.screenX) {
targetX = SelfWrapperState.movingOffsetX + e.screenX;
targetY = SelfWrapperState.movingOffsetY + e.screenY;
} else {
targetX = SelfWrapperState.movingOffsetX + e.touches[0].screenX;
targetY = SelfWrapperState.movingOffsetY + e.touches[0].screenY;
}
PostMessage(window.parent, {
source: WrapperIframeSource,
type: ‘moving’,
data: {
x: targetX,
y: targetY
}
})
}
static stopMoving(e) {
mouseMoveEvent.forEach(function (event) {
document.removeEventListener(event, WrapperIframe.Moving);
})
}
static startMoving(e) {
if (e.screenX) {
SelfWrapperState.movingOffsetX = SelfWrapperState.positionX – e.screenX;
SelfWrapperState.movingOffsetY = SelfWrapperState.positionY – e.screenY;
} else {
SelfWrapperState.movingOffsetX = SelfWrapperState.positionX – e.touches[0].screenX;
SelfWrapperState.movingOffsetY = SelfWrapperState.positionY – e.touches[0].screenY;
}
mouseMoveEvent.forEach(event => {
document.addEventListener(event, WrapperIframe.Moving);
})
mouseUpEvent.forEach(event => {
document.addEventListener(event, WrapperIframe.stopMoving);
})
}
static onStateChange(data) {
SelfWrapperState.positionX = data.left;
SelfWrapperState.positionY = data.top;
SelfWrapperState.sizeX = data.width;
SelfWrapperState.sizeY = data.height;
}
static InitState() {
window.addEventListener(‘message’, (e) => {
if (e.data.source == WrapperIframeSource) {
switch (e.data.type) {
case ‘state’:
WrapperIframe.onStateChange(e.data.data);
break;
}
}
})
PostMessage(window.parent, {
source: WrapperIframeSource,
type: ‘init’,
})
}
}

(async function () {
await topFrameState.asyncInit();
function getTopFrame() {
return topFrameState;
}

function checkVtFrame(frame) {
return frame != undefined && frame.src == ‘https://2gether.video/videotogether_wrapper.html’;
}
WrapperIframe.InitState();

function getDurationStr(duration) {
try {
let d = parseInt(duration);
let str = ""
let units = [" 秒 ", " 分 ", " 小时 "] for (let i in units) {
if (d > 0) {
str = d % 60 + units[i] + str;
}
d = Math.floor(d / 60)
}
return str;
} catch {
return "N/A"
}
}

function downloadEnabled() {
try {
if (window.VideoTogetherDownload == ‘disabled’) {
return false;
}
const type = VideoTogetherStorage.UserscriptType
return parseInt(window.VideoTogetherStorage.LoaddingVersion) >= 1694758378
&& (type == "Chrome" || type == "Safari" || type == "Firefox")
&& !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 != "") {
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 ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}

window.updateM3u8Status = async function updateM3u8Status(m3u8Url, status) {
// 0 downloading 1 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("Failed to read blob"));
};
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: "VideoTogether",
type: 2005,
data: {
table: table,
regex: regex,
id: queryId
}
}, ‘*’)
regexCallback[queryId] = (data) => {
try {
res(data)
} catch { rej() }
}
})
}

saveToIndexedDBThreads = 1;
window.saveToIndexedDB = async function saveToIndexedDB(table, key, data) {
while (saveToIndexedDBThreads setTimeout(r, 100));
}
saveToIndexedDBThreads–;
const queryId = generateUUID();
return new Promise((res, rej) => {
data.saveTime = Date.now()
window.postMessage({
source: "VideoTogether",
type: 2001,
data: {
table: table,
key: key,
data: data,
id: queryId,
}
}, ‘*’)
data = null;
saveCallback[queryId] = (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: "VideoTogether",
type: 3010,
data: {
prefix: prefix,
id: queryId,
}
}, ‘*’)
deleteByPrefix[queryId] = (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 == "VideoTogether") {
switch (e.data.type) {
case 2003: {
saveCallback[e.data.data.id](e.data.data.error)
saveCallback[e.data.data.id] = undefined
break;
}
case 2004: {
readCallback[e.data.data.id](e.data.data.data)
readCallback[e.data.data.id] = undefined;
break;
}
case 2006: {
regexCallback[e.data.data.id](e.data.data.data)
regexCallback[e.data.data.id] = undefined;
break;
}
case 2008: {
deleteCallback[e.data.data.id](e.data.data.error);
deleteCallback[e.data.data.id] = undefined;
break;
}
case 3011: {
deleteByPrefix[e.data.data.id](e.data.data.error);
deleteByPrefix[e.data.data.id] = undefined;
break;
}
case 2010: {
console.log(e.data.data.data);
break;
}
}
}
})
window.requestStorageEstimate = function requestStorageEstimate() {
window.postMessage({
source: "VideoTogether",
type: 2009,
data: {}
}, ‘*’)
}
window.deleteFromIndexedDB = function deleteFromIndexedDB(table, key) {
const queryId = generateUUID()
window.postMessage({
source: "VideoTogether",
type: 2007,
data: {
id: queryId,
table: table,
key: key,
}
}, ‘*’)
return new Promise((res, rej) => {
deleteCallback[queryId] = (error) => {
if (error === 0) {
res(true);
} else {
rej(error);
}
}
})
}

window.readFromIndexedDB = function readFromIndexedDB(table, key) {
const queryId = generateUUID();

window.postMessage({
source: "VideoTogether",
type: 2002,
data: {
table: table,
key: key,
id: queryId,
}
}, ‘*’)
return new Promise((res, rej) => {
readCallback[queryId] = (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("ReadableStream not yet supported in this browser.");
}

const contentType = response.headers.get("Content-Type") || "application/octet-stream";

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[index];
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 && lastRunQueue[0] timeLimitation) {
console.error("limited")
return true;
}
lastRunQueue.push(Date.now() / 1000);
return false;
}

function getVideoTogetherStorage(key, defaultVal) {
try {
if (window.VideoTogetherStorage == undefined) {
return defaultVal
} else {
if (window.VideoTogetherStorage[key] == undefined) {
return defaultVal
} else {
return window.VideoTogetherStorage[key];
}
}
} 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 ? "" : 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’, "115.com", "acfun.cn", "youtube.com",
];
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’, "115.com", "pornhub.com", "acfun.cn", "youtube.com",
// —
"missav.com", "nivod4.tv"
];
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 {
if (isWrapperFrameEnabled && isWrapperFrame) {
return topFrameState.isEasySharePage;
} else {
return window.VideoTogetherEasyShareMemberSite == true;
}
} catch {
return false;
}
}

function useMobileStyle(videoDom) {
let isMobile = false;
if (getTopFrame().url.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 = "100%";
videoDom.style.maxWidth = videoDom.style.maxHeight = "100%";
videoDom.style.display = ‘block’;
if (videoDom.parentElement != document.body) {
document.body.appendChild(videoDom);
}
}

const mediaUrlsCache = {}
function extractMediaUrls(m3u8Content, m3u8Url) {
if (mediaUrlsCache[m3u8Url] == undefined) {
let lines = m3u8Content.split("\n");
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 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 == "") {
e.style.backgroundImage = ‘none’;
} else if (e.style.backgroundImage != `url("${url}")`) {
e.style.backgroundImage = `url("${url}")`
}
}
}

function changeMemberCount(c) {
extension.ctxMemberCount = c;
select(‘#memberCount’).innerHTML = String.fromCodePoint("0x1f465") + " " + 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));
}

function AttachShadow(e, options) {
try {
return e.attachShadow(options);
} catch (err) {
GetNativeFunction();
return Global.NativeAttachShadow.call(e, options);
}
}

async function Fetch(url, init) {
if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.fetch))) {
return await fetch(url, init);
} else {
GetNativeFunction();
return await Global.NativeFetch.call(window, url, init);
}
}
async function sendMessageToTop(type, data) {
PostMessage(window.top, {
source: "VideoTogether",
type: type,
data: data
});
}

async function sendMessageToVt(type, data) {
if (isWrapperFrameEnabled) {
PostMessage(window, {
source: vtMsgSrc,
type: MessageType.ExtMessageTo,
data: {
source: vtMsgSrc,
type: type,
data: data,
target: ‘vtFrame’
}
});
} else {
sendMessageToTop(type, data);
}
}

function sendMessageToSelf(type, data) {
PostMessage(window, {
source: "VideoTogether",
type: type,
data: data
});
}

function sendMessageTo(w, type, data) {
PostMessage(w, {
source: "VideoTogether",
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 {
"method": "/room/update",
"data": {
"tempUser": extension.tempUser,
"password": password,
"name": name,
"playbackRate": playbackRate,
"currentTime": currentTime,
"paused": paused,
"url": url,
"lastUpdateClientTime": localTimestamp,
"duration": duration,
"protected": isRoomProtected(),
"videoTitle": extension.isMain ? topFrameState.title : extension.videoTitle,
"sendLocalTimestamp": Date.now() / 1000,
"m3u8Url": m3u8Url
}
}
}

function WSJoinRoomRequest(name, password) {
return {
"method": "/room/join",
"data": {
"password": password,
"name": name,
}
}
}

function WsUpdateMemberRequest(name, password, isLoadding, currentUrl) {
return {
"method": "/room/update_member",
"data": {
"password": password,
"roomName": name,
"sendLocalTimestamp": Date.now() / 1000,
"userId": extension.tempUser,
"isLoadding": isLoadding,
"currentUrl": currentUrl
}
}
}

function popupError(msg) {
let x = select("#snackbar");
x.innerHTML = msg;
x.className = "show";
setTimeout(function () { x.className = x.className.replace("show", ""); }, 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("https://", "")}/ws?language=${language}`);
this._socket.onmessage = async e => {
let lines = e.data.split(‘\n’);
for (let i = 0; i < lines.length; i++) {
try {
await this.onmessage(lines[i]);
} catch (err) { console.log(err, lines[i]) }
}
}
} catch { }
},
async onmessage(str) {
data = JSON.parse(str);
if (data['errorMessage'] != null) {
this._lastUpdateTime = Date.now() / 1000;
this._lastErrorMessage = data['errorMessage'];
this._lastRoom = null;
return;
}
this._lastErrorMessage = null;
if (data['method'] == "/room/join") {
this._joinedName = data['data']['name'];
}
if (data['method'] == "/room/join" || data['method'] == "/room/update" || data['method'] == "/room/update_member") {
this._connectedToService = true;
this._lastRoom = Object.assign(data['data'], Room);
this._lastUpdateTime = Date.now() / 1000;

if (extension.role == extension.RoleEnum.Member) {
if (!isLimited()) {
extension.ScheduledTask();
}
}
if (extension.role == extension.RoleEnum.Master && data['method'] == "/room/update_member") {
if (!isLimited()) {
extension.setWaitForLoadding(this._lastRoom.waitForLoadding);
extension.ScheduledTask();
}
}
}
if (data['method'] == 'replay_timestamp') {
sendMessageToVt(MessageType.TimestampV2Resp, { ts: Date.now() / 1000, data: data['data'] })
}
if (data['method'] == 'url_req') {
extension.UrlRequest(data['data'].m3u8Url, data['data'].idx, data['data'].origin)
}
if (data['method'] == 'url_resp') {
realUrlCache[data['data'].origin] = data['data'].real;
}
if (data['method'] == 'm3u8_req') {
content = extension.GetM3u8Content(data['data'].m3u8Url);
WS.m3u8ContentResp(data['data'].m3u8Url, content);
}
if (data['method'] == 'm3u8_resp') {
m3u8ContentCache[data['data'].m3u8Url] = data['data'].content;
}
if (data['method'] == 'send_txtmsg' && getEnableTextMessage()) {
popupError("有新消息 (修改语音包)");
extension.gotTextMsg(data[‘data’].id, data[‘data’].msg);
sendMessageToVt(MessageType.GotTxtMsg, { id: data[‘data’].id, msg: data[‘data’].msg });
}
},
getRoom() {
if (this._lastUpdateTime + this._expriedTime > Date.now() / 1000) {
if (this._lastErrorMessage != null) {
throw new Error(this._lastErrorMessage);
}
return this._lastRoom;
}
},
async send(data) {
try {
this._socket.send(JSON.stringify(data));
} catch { }
},
async updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url) {
// TODO localtimestamp
this.send(WSUpdateRoomRequest(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url));
},
async urlReq(m3u8Url, idx, origin) {
this.send({
"method": "url_req",
"data": {
"m3u8Url": m3u8Url,
"idx": idx,
"origin": origin
}
})
},
async urlResp(origin, real) {
this.send({
"method": "url_resp",
"data": {
"origin": origin,
"real": real,
}
})
},
async m3u8ContentReq(m3u8Url) {
this.send({
"method": "m3u8_req",
"data": {
"m3u8Url": m3u8Url,
}
})
},
async sendTextMessage(id, msg) {
this.send({
"method": "send_txtmsg",
"data": {
"msg": msg,
"id": id
}
})
},
async m3u8ContentResp(m3u8Url, content) {
this.send({
"method": "m3u8_resp",
"data": {
"m3u8Url": m3u8Url,
"content": content
}
})
},
async updateMember(name, password, isLoadding, currentUrl) {
this.send(WsUpdateMemberRequest(name, password, isLoadding, currentUrl));
},
_joinedName: null,
async joinRoom(name, password) {
if (name == this._joinedName) {
return;
}
this.send(WSJoinRoomRequest(name, password));
},
async disconnect() {
if (this._socket != null) {
try {
this._socket.close();
} catch { }
}
this._joinedName = null;
this._socket = null;
}
}

const VoiceStatus = {
STOP: 1,
CONNECTTING: 5,
MUTED: 2,
UNMUTED: 3,
ERROR: 4
}

const Voice = {
_status: VoiceStatus.STOP,
_errorMessage: "",
_rname: "",
_mutting: false,
get errorMessage() {
return this._errorMessage;
},
set errorMessage(m) {
this._errorMessage = m;
select("#snackbar").innerHTML = m;
let voiceConnErrBtn = select(‘#voiceConnErrBtn’);
if (voiceConnErrBtn != undefined) {
voiceConnErrBtn.onclick = () => {
alert(‘如果你安装了uBlock等去广告插件,请停用这些去广告插件后再试’)
}
}
},
set status(s) {
this._status = s;
let disabledMic = select("#disabledMic");
let micBtn = select(‘#micBtn’);
let audioBtn = select(‘#audioBtn’);
let callBtn = select("#callBtn");
let callConnecting = select("#callConnecting");
let callErrorBtn = select("#callErrorBtn");
dsply(callConnecting, s == VoiceStatus.CONNECTTING);
dsply(callBtn, s == VoiceStatus.STOP);
let inCall = (VoiceStatus.UNMUTED == s || VoiceStatus.MUTED == s);
dsply(micBtn, inCall);
dsply(audioBtn, inCall);
dsply(callErrorBtn, s == VoiceStatus.ERROR);
switch (s) {
case VoiceStatus.STOP:
break;
case VoiceStatus.MUTED:
show(disabledMic);
break;
case VoiceStatus.UNMUTED:
hide(disabledMic);
break;
case VoiceStatus.ERROR:
var x = select("#snackbar");
x.className = "show";
setTimeout(function () { x.className = x.className.replace("show", ""); }, 3000);
break;
default:
break;
}
},
get status() {
return this._status;
},
_conn: null,
set conn(conn) {
this._conn = conn;
},
/**
* @return {RTCPeerConnection}
*/
get conn() {
return this._conn
},

_stream: null,
set stream(s) {
this._stream = s;
},
/**
* @return {MediaStream}
*/
get stream() {
return this._stream;
},

_noiseCancellationEnabled: true,
set noiseCancellationEnabled(n) {
this._noiseCancellationEnabled = n;
if (this.inCall) {
this.updateVoiceSetting(n);
}
},

get noiseCancellationEnabled() {
return this._noiseCancellationEnabled;
},

get inCall() {
return this.status == VoiceStatus.MUTED || this.status == VoiceStatus.UNMUTED;
},

join: async function (name, rname, mutting = false) {
Voice._rname = rname;
Voice._mutting = mutting;
let cancellingNoise = true;
try {
cancellingNoise = !(window.VideoTogetherStorage.EchoCancellation === false);
} catch { }

Voice.stop();
Voice.status = VoiceStatus.CONNECTTING;
this.noiseCancellationEnabled = cancellingNoise;
let uid = generateUUID();
let notNullUuid;
try {
notNullUuid = await waitForRoomUuid();
} catch {
Voice.errorMessage = "uuid缺失";
Voice.status = VoiceStatus.ERROR;
return;
}
const rnameRPC = fixedEncodeURIComponent(notNullUuid + "_" + rname);
if (rnameRPC.length > 256) {
Voice.errorMessage = "房间名太长";
Voice.status = VoiceStatus.ERROR;
return;
}
if (window.location.protocol != "https:" && window.location.protocol != ‘file:’) {
Voice.errorMessage = "仅支持https网站使用";
Voice.status = VoiceStatus.ERROR;
return;
}
const unameRPC = fixedEncodeURIComponent(uid + ‘:’ + Base64.encode(generateUUID()));
let ucid = "";
console.log(rnameRPC, uid);
const configuration = {
bundlePolicy: ‘max-bundle’,
rtcpMuxPolicy: ‘require’,
sdpSemantics: ‘unified-plan’
};

async function subscribe(pc) {
var res = await rpc(‘subscribe’, [rnameRPC, unameRPC, ucid]);
if (res.error && typeof res.error === ‘object’ && typeof res.error.code === ‘number’ && [5002001, 5002002].indexOf(res.error.code) != -1) {
Voice.join("", Voice._rname, Voice._mutting);
return;
}
if (res.data) {
var jsep = JSON.parse(res.data.jsep);
if (jsep.type == ‘offer’) {
await pc.setRemoteDescription(jsep);
var sdp = await pc.createAnswer();
await pc.setLocalDescription(sdp);
await rpc(‘answer’, [rnameRPC, unameRPC, ucid, JSON.stringify(sdp)]);
}
}
setTimeout(function () {
if (Voice.conn != null && pc === Voice.conn && Voice.status != VoiceStatus.STOP) {
subscribe(pc);
}
}, 3000);
}

try {
await start();
} catch (e) {
if (Voice.status == VoiceStatus.CONNECTTING) {
Voice.status = VoiceStatus.ERROR;
Voice.errorMessage = "连接失败 (帮助)";
}
}

if (Voice.status == VoiceStatus.CONNECTTING) {
Voice.status = mutting ? VoiceStatus.MUTED : VoiceStatus.UNMUTED;
}

async function start() {

let res = await rpc(‘turn’, [unameRPC]);
if (res.data && res.data.length > 0) {
configuration.iceServers = res.data;
configuration.iceTransportPolicy = ‘relay’;
}

Voice.conn = new RTCPeerConnection(configuration);

Voice.conn.onicecandidate = ({ candidate }) => {
rpc(‘trickle’, [rnameRPC, unameRPC, ucid, JSON.stringify(candidate)]);
};

Voice.conn.ontrack = (event) => {
console.log("ontrack", event);

let stream = event.streams[0];
let sid = fixedDecodeURIComponent(stream.id);
let id = sid.split(‘:’)[0];
// var name = Base64.decode(sid.split(‘:’)[1]);
console.log(id, uid);
if (id === uid) {
return;
}
event.track.onmute = (event) => {
console.log("onmute", 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 = "麦克风权限获取失败";
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’, [rnameRPC, unameRPC, JSON.stringify(Voice.conn.localDescription)]);
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 == "disconnected" || Voice.conn.iceConnectionState == "failed" || Voice.conn.iceConnectionState == "closed") {
Voice.errorMessage = "连接断开";
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 + "/kraken", "POST", { 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 "Content-Type" 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("*")].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 ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}

function generateTempUserId() {
return generateUUID() + ":" + Date.now() / 1000;
}

/**
*
* Base64 encode / decode
* http://www.webtoolkit.info
*
**/
const Base64 = {

// private property
_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

// public method for encoding
, encode: function (input) {
var output = "";
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 = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;

input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
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 = "";
string = string.replace(/\r\n/g, "\n");

for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);

if (c 127) && (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 = "";
var i = 0;
var c, c1, c2, c3;
c = c1 = c2 = 0;

while (i < utftext.length) {
c = utftext.charCodeAt(i);

if (c 191) && (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("#memberCount").innerText = extension.ctxMemberCount;
qs("#send-button").disabled = !extension.ctxWsIsOpen;
} catch { };
if (document.fullscreenElement.contains(this.fullscreenSWrapper)) {
return;
}
let shadowWrapper = document.createElement("div");
this.fullscreenSWrapper = shadowWrapper;
shadowWrapper.id = "VideoTogetherfullscreenSWrapper";
let wrapper;
try {
wrapper = AttachShadow(shadowWrapper, { mode: "open" });
wrapper.addEventListener(‘keydown’, (e) => e.stopPropagation());
this.fullscreenWrapper = wrapper;
} catch (e) { console.error(e); }
wrapper.innerHTML = `
.container {
position: absolute;
top: 50%;
left: 0px;
border: 1px solid #000;
padding: 0px;
display: flex;
align-items: center;
justify-content: space-between;
width: fit-content;
justify-content: center;
border-radius: 5px;
opacity: 80%;
background: #000;
color: white;
z-index: 2147483647;
}

.container input[type=’text’] {
padding: 0px;
flex-grow: 1;
border: none;
height: 24px;
width: 0px;
height: 32px;
transition: width 0.1s linear;
background-color: transparent;
color: white;
}

.container input[type=’text’].expand {
width: 150px;
}

.container .user-info {
display: flex;
align-items: center;
}

.container button {
height: 32px;
font-size: 16px;
border: 0px;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: #1890ff;
transition-duration: 0.4s;
border-radius: 4px;
}

.container #expand-button {
color: black;
font-weight: bolder;
height: 32px;
width: 32px;
background-size: cover;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACrFBMVEXg9b7e87jd87jd9Lnd9Lre9Lng9b/j98jm98vs99fy9ubu89/e1sfJqKnFnqLGoaXf9Lvd87Xe87fd8rfV67Ti9sbk98nm9sze48TX3rjU1rTKr6jFnaLe9Lfe87Xe9LjV7LPN4q3g78PJuqfQ1a7OzarIsabEnaHi9sXd8rvd8rbd87axx4u70Jrl+cvm+szQxq25lZTR1a7KvaXFo6LFnaHEnKHd6r3Y57TZ7bLb8bTZ7rKMomClun/k+MrOx6yue4PIvqfP06vLv6fFoqLEnKDT27DS3a3W6K7Y7bDT6auNq2eYn3KqlYShYXTOwLDAzZ7MyanKtqbEoaHDm6DDm5/R2K3Q2KzT4q3W6a7P3amUhWp7SEuMc2rSyri3zJe0xpPV17TKuqbGrqLEnqDQ2K3O06rP0arR2qzJx6GZX160j4rP1LOiuH2GnVzS3rXb47zQ063OzanHr6PDnaDMxajIsaXLwKfEt5y6mI/GyqSClVZzi0bDzp+8nY/d6L/X4rbQ1qzMyKjEqKHFpqLFpaLGqaO2p5KCjlZ5jky8z5izjoOaXmLc5r3Z57jU4K7S3K3NyqnBm56Mg2KTmWnM0KmwhH2IOUunfXnh8cXe8b7Z7LPV4rDBmZ3Cmp+6mZWkk32/qZihbG97P0OdinXQ3rTk+Mjf9L/d8rja6ri9lpqnh4qhgoWyk5Kmd3qmfHW3oou2vZGKpmaUrXDg9MPf9L3e876yj5Ori42Mc3aDbG6MYmyifXfHyaPU3rHH0aKDlVhkejW70Zbf9bze87be87ng9cCLcnWQd3qEbG9/ZmmBXmSflYS4u5ra5Lnd6r7U5ba2ypPB153c87re9b2Ba22EbW+AamyDb3CNgXmxsZng7sTj9sjk98rk+Mng9cHe9Lze9Lrd87n////PlyWlAAAAAWJLR0TjsQauigAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+YGGQYXBzHy0g0AAAEbSURBVBjTARAB7/4AAAECAwQFBgcICQoLDA0ODwAQEREREhMUFRYXGBkaGxwOAAYdHhEfICEWFiIjJCUmDicAKCkqKx8sLS4vMDEyMzQ1NgA3ODk6Ozw9Pj9AQUJDRDVFAEZHSElKS0xNTk9QUVJTVFUAVldYWVpbXF1eX2BhYmNkVABlZmdoaWprbG1ub3BxcnN0AEJ1dnd4eXp7fH1+f4CBgoMAc4QnhYaHiImKi4yNjo+QkQBFVFU2kpOUlZaXmJmam5ucAFRVnZ6foKGio6SlpqeoE6kAVaqrrK2ur7CxsrO0tQEDtgC3uLm6u7y9vr/AwcLDxMXGAMfIycrLzM3Oz9DR0tMdAdQA1da619jZ2tvc3d7f4OEB4iRLaea64H7qAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIyLTA2LTI1VDA2OjIzOjAyKzAwOjAwlVQlhgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMi0wNi0yNVQwNjoyMzowMiswMDowMOQJnToAAAAgdEVYdHNvZnR3YXJlAGh0dHBzOi8vaW1hZ2VtYWdpY2sub3JnvM8dnQAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABh0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQAMTkyQF1xVQAAABd0RVh0VGh1bWI6OkltYWdlOjpXaWR0aAAxOTLTrCEIAAAAGXRFWHRUaHVtYjo6TWltZXR5cGUAaW1hZ2UvcG5nP7JWTgAAABd0RVh0VGh1bWI6Ok1UaW1lADE2NTYxMzgxODJHYkS0AAAAD3RFWHRUaHVtYjo6U2l6ZQAwQkKUoj7sAAAAVnRFWHRUaHVtYjo6VVJJAGZpbGU6Ly8vbW50bG9nL2Zhdmljb25zLzIwMjItMDYtMjUvNGU5YzJlYjRjNmRhMjIwZDgzYjcyOTYxZmI1ZTJiY2UuaWNvLnBuZ7tNVVEAAAAASUVORK5CYII=);
}

.container #close-btn {
height: 16px;
max-width: 24px;
background-color: rgba(255, 0, 0, 0.5);
font-size: 8px;
}

.container #close-btn:hover {
background-color: rgba(255, 0, 0, 0.3);
}

.container button:hover {
background-color: #6ebff4;
}

.container button:disabled,
.container button:disabled:hover {
background-color: rgb(76, 76, 76);
}

👥
0

`;
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 = "none"; }
wrapper.getElementById(‘expand-button’).addEventListener(‘click’, () => expand());
sendBtn.onclick = () => {
extension.currentSendingMsgId = generateUUID();
sendMessageToVt(MessageType.SendTxtMsg, { currentSendingMsgId: extension.currentSendingMsgId, value: msgInput.value });
}
GotTxtMsgCallback = (id, msg) => {
console.log(id, msg);
if (id == extension.currentSendingMsgId && msg == msgInput.value) {
msgInput.value = "";
}
}
msgInput.addEventListener("keyup", e => {
if (e.key == "Enter") {
sendBtn.click();
}
});
} else {
if (this.fullscreenSWrapper != undefined) {
this.fullscreenSWrapper.remove();
this.fullscreenSWrapper = undefined;
this.fullscreenWrapper = undefined;
GotTxtMsgCallback = undefined;
}
}
}, 500);
if (this.isMain) {
document.addEventListener("click", () => {
this.enableSpeechSynthesis();
});
this.minimized = false;
let shadowWrapper = document.createElement("div");
shadowWrapper.id = "VideoTogetherWrapper";
shadowWrapper.ontouchstart = (e) => { e.stopPropagation() }
let wrapper;
try {
wrapper = AttachShadow(shadowWrapper, { mode: "open" });
wrapper.addEventListener(‘keydown’, (e) => e.stopPropagation())
} catch (e) { console.error(e); }

this.shadowWrapper = shadowWrapper;
this.wrapper = wrapper;
wrapper.innerHTML = `

VideoTogether











房间

密码

连接文字聊天服务器中…
缺少中文语音包

检测视频中…


下载中,不要关闭页面
下载完成

视频音量

通话音量

IOS不支持音量调节

<!–

通话降噪

–>


《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣

:host {
all: initial;
font-size: 14px;
font-family: Arial, sans-serif;
}

#videoTogetherFlyPannel {
user-select: none;
background-color: #ffffff !important;
display: block;
z-index: 2147483647;
position: fixed;
bottom: 0px;
right: 0px;
width: 260px;
height: 210px;
text-align: center;
border: solid 1px #e9e9e9 !important;
/* box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d; */
border-radius: 10px;
line-height: 1.2;
}

#videoTogetherFlyPannel #videoTogetherHeader {
cursor: move;
touch-action: none;
align-items: center;
display: flex;
}

.vt-modal-content {
/* position: relative; */
width: 100%;
height: 100%;
}

#roomButtonGroup,
#lobbyBtnGroup,
.content {
display: contents;
}

.vt-modal-audio {
position: absolute;
top: 10px;
right: 140px;
}

.vt-modal-mic {
position: absolute;
top: 10px;
right: 100px;
}

.vt-modal-setting {
position: absolute;
top: -1px;
right: 65px;
}

.vt-modal-easyshare {
position: absolute;
top: -1px;
right: 90px;
}

.vt-modal-donate {
position: absolute;
top: -1px;
right: 40px;
}

.vt-modal-title-button {
z-index: 10;
padding: 0;
color: #6c6c6c;
font-weight: 700;
line-height: 1;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
transition: color .3s;
}

.vt-modal-close {
position: absolute;
top: 0;
right: 15px;
}

.vt-modal-close-x {
width: 18px;
height: 46px;
font-size: 16px;
font-style: normal;
line-height: 46px;
text-align: center;
text-transform: none;
text-rendering: auto;
display: flex;
align-items: center;
justify-content: center;
}

.vt-modal-close-x:hover {
color: #1890ff;
}

.error-button {
color: #ff6f72;
}

.error-button:hover {
color: red;
}

.vt-modal-header {
display: flex;
padding: 12px;
color: #000000d9;
background: #fff;
border-bottom: 1px solid #f0f0f0;
border-radius: 10px 10px 0 0;
align-items: center;
}

.vt-modal-title {
margin: 0;
margin-left: 10px;
color: #000000d9;
font-weight: 500;
font-size: 16px;
line-height: 22px;
word-wrap: break-word;
}

.vt-modal-body {
height: 164px;
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
font-size: 16px;
color: black;
border-radius: 0 0 10px 10px;
background-size: cover;
}

.vt-modal-footer {
padding: 10px 16px;
text-align: right;
background: transparent;
border-top: 1px solid #f0f0f0;
border-radius: 0 0 2px 2px;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}

.vt-btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
touch-action: manipulation;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
border-color: #d9d9d9;
background: #fff;
outline: 0;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
}

.vt-btn:hover {
border-color: #e3e5e7 !important;
background-color: #e3e5e7 !important;
}

.vt-btn-primary {
color: #fff;
border-color: #1890ff;
background: #1890ff !important;
}

.vt-btn-primary:hover {
border-color: #6ebff4 !important;
background-color: #6ebff4 !important;
}

.vt-btn-secondary {
color: #fff;
border-color: #23d591;
background: #23d591 !important;
}

.vt-btn-secondary:hover {
border-color: #8af0bf !important;
background-color: #8af0bf !important;
}

.vt-btn-dangerous {
color: #fff;
border-color: #ff4d4f;
background-color: #ff4d4f;
}

.vt-btn-dangerous:hover {
border-color: #f77173 !important;
background-color: #f77173 !important;
}

.vt-modal-content-item {
cursor: pointer;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.16);
padding: 0 12px;
width: 45%;
height: 60px;
margin-bottom: 12px;
display: flex;
align-items: center;
}

.vt-modal-content-item:hover {
background-color: #efefef;
}

#videoTogetherSamllIcon {
z-index: 2147483647;
position: fixed;
bottom: 15px;
right: 15px;
text-align: center;
}

#videoTogetherRoomNameLabel,
#videoTogetherRoomPasswordLabel {
display: inline-block;
width: 76px;
}

#videoTogetherRoomNameInput:disabled {
border: none;
background-color: transparent;
color: black;
}

#videoTogetherRoomNameInput,
#videoTogetherRoomPdIpt {
width: 150px;
height: auto;
font-family: inherit;
font-size: inherit;
display: inline-block;
padding: 0;
color: #00000073;
background-color: #ffffff;
border: 1px solid #e9e9e9;
margin: 0;
}

.lds-ellipsis {
display: inline-block;
position: relative;
width: 80px;
height: 32px;
}

.lds-ellipsis div {
position: absolute;
top: 8px;
width: 13px;
height: 13px;
border-radius: 50%;
background: #6c6c6c;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}

.lds-ellipsis div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}

.lds-ellipsis div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}

.lds-ellipsis div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}

.lds-ellipsis div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}

@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}

100% {
transform: scale(1);
}
}

@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}

100% {
transform: scale(0);
}
}

@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}

100% {
transform: translate(24px, 0);
}
}

.range-slider {
margin: 0px 0 0 0px;
display: inline-block;
}

.range-slider {
width: 130px
}

.slider {
-webkit-appearance: none;
width: calc(100% – (0px));
height: 5px;
border-radius: 5px;
background: #d7dcdf;
outline: none;
padding: 0;
margin: 0;
}

.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 10px;
height: 10px;
border-radius: 50%;
background: #2c3e50;
cursor: pointer;
-webkit-transition: background 0.15s ease-in-out;
transition: background 0.15s ease-in-out;
}

.slider::-moz-range-progress {
background-color: #1abc9c;
}

.slider::-webkit-slider-thumb:hover {
background: #1abc9c;
}

.slider:active::-webkit-slider-thumb {
background: #1abc9c;
}

.slider::-moz-range-thumb {
width: 10px;
height: 10px;
border: 0;
border-radius: 50%;
background: #2c3e50;
cursor: pointer;
-moz-transition: background 0.15s ease-in-out;
transition: background 0.15s ease-in-out;
}

.slider::-moz-range-thumb:hover {
background: #1abc9c;
}

.slider:active::-moz-range-thumb {
background: #1abc9c;
}

::-moz-range-track {
background: #d7dcdf;
border: 0;
}

input::-moz-focus-inner,
input::-moz-focus-outer {
border: 0;
}

.toggler-wrapper {
display: inline-block;
width: 45px;
height: 20px;
cursor: pointer;
position: relative;
}

.toggler-wrapper input[type="checkbox"] {
display: none;
}

.toggler-wrapper input[type="checkbox"]:checked+.toggler-slider {
background-color: #1abc9c;
}

.toggler-wrapper .toggler-slider {
margin-top: 4px;
background-color: #ccc;
position: absolute;
border-radius: 100px;
top: 0;
left: 0;
width: 100%;
height: 100%;
-webkit-transition: all 300ms ease;
transition: all 300ms ease;
}

.toggler-wrapper .toggler-knob {
position: absolute;
-webkit-transition: all 300ms ease;
transition: all 300ms ease;
}

.toggler-wrapper.style-1 input[type="checkbox"]:checked+.toggler-slider .toggler-knob {
left: calc(100% – 16px – 3px);
}

.toggler-wrapper.style-1 .toggler-knob {
width: calc(20px – 6px);
height: calc(20px – 6px);
border-radius: 50%;
left: 3px;
top: 3px;
background-color: #fff;
}

#snackbar {
visibility: hidden;
width: auto;
background-color: #333;
color: #fff;
text-align: center;
padding: 16px 0px 16px 0px;
position: relative;
z-index: 999999;
top: -56px;
}

#snackbar.show {
visibility: visible;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}

@keyframes fadein {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

@keyframes fadeout {
from {
opacity: 1;
}

to {
opacity: 0;
}
}

#downloadProgress {
display: flex;
flex-direction: column;
width: 80%;
align-items: center;
margin: auto;
}

#speedAndStatus {
display: flex;
justify-content: space-between;
}

#downloadPannel {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
justify-content: space-between;
}

#downloadVideoInfo {
display: block;
}
`;
(document.body || document.documentElement).appendChild(shadowWrapper);

wrapper.querySelector("#videoTogetherMinimize").onclick = () => { this.Minimize() }
wrapper.querySelector("#videoTogetherMaximize").onclick = () => { this.Maximize() }
["", "webkit"].forEach(prefix => {
document.addEventListener(prefix + "fullscreenchange", (event) => {
if (document.fullscreenElement || document.webkitFullscreenElement) {
hide(this.videoTogetherFlyPannel);
hide(this.videoTogetherSamllIcon);
} else {
if (this.minimized) {
this.Minimize();
} else {
this.Maximize();
}
}
});
});
wrapper.querySelector("#textMessageInput").addEventListener("keyup", e => {
if (e.key == "Enter") {
wrapper.querySelector("#textMessageSend").click();
}
});
wrapper.querySelector("#textMessageSend").onclick = async () => {
extension.currentSendingMsgId = generateUUID();
WS.sendTextMessage(extension.currentSendingMsgId, select("#textMessageInput").value);
}
this.lobbyBtnGroup = wrapper.querySelector("#lobbyBtnGroup");
this.createRoomButton = wrapper.querySelector(‘#videoTogetherCreateButton’);
this.joinRoomButton = wrapper.querySelector("#videoTogetherJoinButton");
this.roomButtonGroup = wrapper.querySelector(‘#roomButtonGroup’);
this.exitButton = wrapper.querySelector("#videoTogetherExitButton");
this.callBtn = wrapper.querySelector("#callBtn");
this.callBtn.onclick = () => Voice.join("", window.videoTogetherExtension.roomName);
this.helpButton = wrapper.querySelector("#videoTogetherHelpButton");
this.audioBtn = wrapper.querySelector("#audioBtn");
this.micBtn = wrapper.querySelector("#micBtn");
this.videoVolume = wrapper.querySelector("#videoVolume");
this.callVolumeSlider = wrapper.querySelector("#callVolume");
this.callErrorBtn = wrapper.querySelector("#callErrorBtn");
this.easyShareCopyBtn = wrapper.querySelector("#easyShareCopyBtn");
this.textMessageChat = wrapper.querySelector("#textMessageChat");
this.textMessageConnecting = wrapper.querySelector("#textMessageConnecting");
this.textMessageConnectingStatus = wrapper.querySelector("#textMessageConnectingStatus");
this.zhcnTtsMissing = wrapper.querySelector("#zhcnTtsMissing");
this.downloadBtn = wrapper.querySelector("#downloadBtn");
hide(this.downloadBtn);
this.confirmDownloadBtn = wrapper.querySelector("#confirmDownloadBtn")
this.confirmDownloadBtn.onclick = () => {
if (extension.downloadM3u8UrlType == "video") {
extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_video_download")
console.log(extension.downloadM3u8Url, extension.downloadM3u8UrlType)
sendMessageToVt(MessageType.SetStorageValue, {
key: "PublicNextDownload", value: {
filename: topFrameState.title + ‘.mp4’,
url: extension.downloadM3u8Url
}
});
const a = document.createElement("a");
a.href = extension.downloadM3u8Url;
a.target = "_blank";
a.download = topFrameState.title + ".mp4";
a.click();
return;
}
extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_m3u8_download")
isDownloading = true;
const m3u8url = extension.downloadM3u8Url
sendMessageTo(extension.m3u8PostWindows[extension.GetM3u8WindowId(m3u8url)], MessageType.StartDownload, {
m3u8Url: m3u8url,
m3u8Content: extension.GetM3u8Content(m3u8url),
urls: extension.GetAllM3u8SegUrls(m3u8url),
title: topFrameState.title,
pageUrl: window.location.href
});

hide(this.confirmDownloadBtn);
show(select("#downloadProgress"));
}
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 = "检测视频中…"
}
}, 1000);
inDownload = true;
this.inputRoomName.value = "download_" + 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(getTopFrame().url, extension.RoleEnum.Member, false))
} else {
await navigator.clipboard.writeText("点击链接,和我一起看吧:

, 如果打不开可以尝试备用链接:"
.replace("
", extension.generateEasyShareLink())
.replace("", extension.generateEasyShareLink(true)));
}
popupError("复制成功,快去分享吧");
} catch {
popupError("复制失败");
}
}
this.callErrorBtn.onclick = () => {
Voice.join("", window.videoTogetherExtension.roomName);
}
this.videoVolume.oninput = () => {
extension.videoVolume = this.videoVolume.value;
sendMessageToVt(MessageType.ChangeVideoVolume, { volume: extension.getVideoVolume() / 100 });
}
this.callVolumeSlider.oninput = () => {
extension.voiceVolume = this.callVolumeSlider.value;
[…select(‘#peer’).querySelectorAll("*")].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("#videoTogetherRoleText")
this.videoTogetherSetting = wrapper.querySelector("#videoTogetherSetting");
hide(this.videoTogetherSetting);
this.inputRoomName = wrapper.querySelector(‘#videoTogetherRoomNameInput’);
this.inputRoomPassword = wrapper.querySelector("#videoTogetherRoomPdIpt");
this.inputRoomNameLabel = wrapper.querySelector(‘#videoTogetherRoomNameLabel’);
this.inputRoomPasswordLabel = wrapper.querySelector("#videoTogetherRoomPasswordLabel");
this.videoTogetherHeader = wrapper.querySelector("#videoTogetherHeader");
this.videoTogetherFlyPannel = wrapper.getElementById("videoTogetherFlyPannel");
this.videoTogetherSamllIcon = wrapper.getElementById("videoTogetherSamllIcon");

this.volume = 1;
this.statusText = wrapper.querySelector("#videoTogetherStatusText");
this.InLobby(true);
this.Init();
setInterval(() => {
this.ShowPannel();
}, 1000);
}

try {
document.querySelector("#videoTogetherLoading").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 = "videoTogetherTxtMsgTouch";
touch.style.width = "100%";
touch.style.height = "100%";
touch.style.position = "fixed";
touch.style.top = "0";
touch.style.left = "0";
touch.style.zIndex = "2147483647";
touch.style.background = "#fff";
touch.style.display = "flex";
touch.style.justifyContent = "center";
touch.style.alignItems = "center";
touch.style.padding = "0px";
touch.style.flexDirection = "column";
touch.style.lineHeight = "40px";
AttachShadow(this.txtMsgTouchPannel, { mode: "open" })
touch.addEventListener(‘click’, function () {
windowPannel.enableSpeechSynthesis();
document.body.removeChild(touch);
windowPannel.txtMsgTouchPannel = undefined;
});
document.body.appendChild(touch);

this.setTxtMsgTouchPannelText("VideoTogether: 您有一条新消息,点击屏幕接收");
}

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 = "连接文字聊天服务器中…"
show(this.textMessageConnectingStatus);
}
if (type == 3) {
show(this.textMessageConnecting);
show(this.zhcnTtsMissing);
}
if (type == 4) {
show(this.textMessageConnecting);
this.textMessageConnectingStatus.innerText = "文字聊天已关闭"
show(this.textMessageConnectingStatus);
}
}

enableSpeechSynthesis() {
if (!extension.speechSynthesisEnabled) {
try {
extension.gotTextMsg("", "", true);
extension.speechSynthesisEnabled = true;
} catch { }
}
}

setTxtMsgTouchPannelText(s) {
let span = document.createElement(‘span’);
span.style.fontSize = "40px";
span.style.lineHeight = "40px";
span.style.color = "black";
span.style.overflowWrap = "break-word";
span.style.textAlign = "center";
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 = "你可以在下方选择朗读信息的语音:";
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);
sendMessageToVt(MessageType.SetStorageValue, { key: "PublicMessageVoice", value: voiceSelect.value });
}
voiceSelect.style.fontSize = "20px";
voiceSelect.style.height = "50px";
voiceSelect.style.maxWidth = "100%";
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("VideoTogetherMinimizedHere", minimized ? 1 : 0)
}

Init() {
let VideoTogetherMinimizedHere = localStorage.getItem("VideoTogetherMinimizedHere");
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 = "";
hide(this.inputRoomPasswordLabel);
hide(this.inputRoomPassword);
this.inputRoomName.placeholder = "";
this.isInRoom = true;
hide(this.downloadBtn)
}

InLobby(init = false) {
if (!init) {
this.Maximize();
}
this.inputRoomName.disabled = false;
this.inputRoomPasswordLabel.style.display = "inline-block";
this.inputRoomPassword.style.display = "inline-block";
this.inputRoomName.placeholder = "请输入房间名"
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 == "website") {
url = "https://2gether.video/guide/website_qa.html"
}
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 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 = ["video", "bwp-video", "fake-iframe-video"]

this.timer = 0
this.roomName = ""
this.roomPassword = ""
this.role = this.RoleEnum.Null
this.url = ""
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 = ‘1707552719’;
this.isMain = isVtFrame;
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("securitypolicyviolation", (e) => {
let host = (new URL(e.blockedURI)).host;
this.cspBlockedHost[host] = 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("*");
for (let i = 0; i speechSynthesis.getVoices().length) {
return;
}
if (!prepare && !extension.speechSynthesisEnabled) {
windowPannel.ShowTxtMsgTouchPannel();
for (let i = 0; i setTimeout(r, 100));
}
}
try {
if (id == this.currentSendingMsgId && msg == select("#textMessageInput").value) {
select("#textMessageInput").value = "";
}
} 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()[idx];
}
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("房主");
break;
case this.RoleEnum.Member:
setRoleText("成员");
break;
default:
setRoleText("");
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("fetch in child");
throw new Error("fetch in child");
}
url = new URL(url);
url.searchParams.set("version", this.version);
try {
url.searchParams.set("language", language);
url.searchParams.set("voiceStatus", this.isMain ? Voice.status : this.voiceStatus);
url.searchParams.set("loaddingVersion", window.VideoTogetherStorage.LoaddingVersion);
url.searchParams.set("runtimeType", window.VideoTogetherStorage.UserscriptType);
} catch (e) { }
try {
url.searchParams.set("userId", window.VideoTogetherStorage.PublicUserId);
} catch (e) { }
url = url.toString();
let host = (new URL(url)).host;
if (this.cspBlockedHost[host] || 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);
})
sendMessageToVt(MessageType.FetchRequest, {
id: id,
url: url.toString(),
method: method,
data: data,
});
setTimeout(() => {
try {
if (this.callbackMap.has(id)) {
this.callbackMap.get(id)({ error: "超时" });
}
} finally {
this.callbackMap.delete(id);
}
}, 20000);
});
}

try {
if (/\{\s+\[native code\]/.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("iqiyi.com")) {
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("disneyplus.com")) {
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("loading");
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("netflix.com")) {
try {
let videoPlayer = netflix.appContext.state.playerApp.getAPI().videoPlayer;
let player = videoPlayer.getVideoPlayerBySessionId(videoPlayer.getAllPlayerSessionIds()[0]);
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[i].VideoTogetherDisabled) {
continue;
}
} catch { };
try {
if (window.location.hostname.endsWith('bilibili.com')) {
if (!!videos[i].closest('div.video-page-card-small') || !!videos[i].closest('div.feed-card')) {
// this is a thumbnail video
continue
}
}
} catch { }
await func(videos[i]);
} catch (e) { console.error(e) };
}
});
}

sendMessageToSonWithContext(type, data) {
if (this.isMain) {
this.ctxRole = this.role;
}
const postData = {
source: "VideoTogether",
type: type,
data: data,
context: {
tempUser: this.tempUser,
videoTitle: this.isMain ? topFrameState.title : this.videoTitle,
voiceStatus: this.isMain ? Voice.status : this.voiceStatus,
VideoTogetherStorage: window.VideoTogetherStorage,
timeOffset: this.timeOffset,
ctxRole: this.ctxRole,
ctxMemberCount: this.ctxMemberCount,
ctxWsIsOpen: this.ctxWsIsOpen
}
};
if (isWrapperFrame) {
PostMessage(window.top, postData);
return;
}
let iframs = document.getElementsByTagName("iframe");
for (let i = 0; i {
let id = setInterval(() => {
if (realUrlCache[originUrl] != undefined) {
res(realUrlCache[originUrl]);
clearInterval(id);
}
}, 200);
setTimeout(() => {
clearInterval(id);
rej(null);
}, 3000);
});
}

async FetchRemoteM3u8Content(m3u8Url) {
if (m3u8ContentCache[m3u8Url] != undefined) {
return m3u8ContentCache[m3u8Url];
}
WS.m3u8ContentReq(m3u8Url);
return new Promise((res, rej) => {
let id = setInterval(() => {
if (m3u8ContentCache[m3u8Url] != undefined) {
res(m3u8ContentCache[m3u8Url]);
clearInterval(id);
}
})
setTimeout(() => {
clearInterval(id);
rej(null);
}, 3000)
})
}

GetM3u8Content(m3u8Url) {
let m3u8Content = "";
for (let id in this.m3u8Files) {
this.m3u8Files[id].forEach(m3u8 => {
if (m3u8Url == m3u8.m3u8Url) {
m3u8Content = m3u8.m3u8Content;
}
})
}
return m3u8Content;
}

GetM3u8WindowId(m3u8Url) {
let windowId = undefined;
for (let id in this.m3u8Files) {
this.m3u8Files[id].forEach(m3u8 => {
if (m3u8Url == m3u8.m3u8Url) {
windowId = id;
}
})
}
return windowId;
}

UrlRequest(m3u8Url, idx, origin) {
for (let id in this.m3u8Files) {
this.m3u8Files[id].forEach(m3u8 => {
if (m3u8Url == m3u8.m3u8Url) {
let urls = extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url);
let url = urls[idx];
sendMessageTo(this.m3u8PostWindows[id], 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[testUrl] = ‘video’
}
}
document.addEventListener("securitypolicyviolation", onsecuritypolicyviolation)
if (this.m3u8UrlTestResult[testUrl] != undefined) {
return this.m3u8UrlTestResult[testUrl];
}
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[testUrl] == undefined) {
this.m3u8UrlTestResult[testUrl] = tp
}
res(this.m3u8UrlTestResult[testUrl])
}
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("securitypolicyviolation", onsecuritypolicyviolation)
})
})
}

// download
GetAllM3u8SegUrls(m3u8Url) {
for (let id in this.m3u8Files) {
for (let mid in this.m3u8Files[id]) {
let m3u8 = this.m3u8Files[id][mid] if (m3u8Url == m3u8.m3u8Url) {
return extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url);
}
}
}
}

// end of download

UpdateStatusText(text, color) {
if (!isWrapperFrame) {
sendMessageToVt(MessageType.UpdateStatusText, { text: text + "", color: color });
} else {
window.videoTogetherFlyPannel.UpdateStatusText(text + "", color);
}
}

async processReceivedMessage(type, data, _msg) {
let _this = this;
// console.info("get ", 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, "red");
}
}
})
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[id].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, "red");
}
}
})
this.sendMessageToSonWithContext(type, data);
break;
case MessageType.GetRoomData:
this.duration = data["duration"];
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 != "") {
currentUrl.hash = "";
newUrl.hash = "";
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("a");
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[i + 1].startsWith(‘#’)) {
continue;
}
let durationLine = lines[i];
let durationParts = durationLine.split(‘:’);
if (durationParts.length > 1) {
let durationValue = durationParts[1].split(‘,’)[0];
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("data:")) {
m3u8.m3u8Url = `${cyrb53(m3u8.m3u8Url)}`;
}
if (this.m3u8DurationReCal[m3u8.m3u8Url] == undefined) {
this.m3u8DurationReCal[m3u8.m3u8Url] = calculateM3U8Duration(m3u8.m3u8Content);
}
m3u8.duration = this.m3u8DurationReCal[m3u8.m3u8Url];
} catch { }
})
this.m3u8Files[data[‘id’]] = data[‘m3u8Files’];
this.m3u8PostWindows[data[‘id’]] = _msg.source;
break;
}
case MessageType.FetchRealUrlReq: {
console.log(data);
if (realUrlCache[data.url] == undefined) {
const controller = new AbortController();
let r = await Fetch(data.url, {
method: "GET",
signal: controller.signal
});
controller.abort();
realUrlCache[data.url] = r.url;
}
sendMessageToVt(MessageType.FetchRealUrlResp, { origin: data.origin, real: realUrlCache[data.url] });
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.origin] = 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: "VideoTogether",
type: 2012,
data: data
});
break;
}
case MessageType.StartDownload: {
startDownload(data.m3u8Url, data.m3u8Content, data.urls, data.title, data.pageUrl);
setInterval(() => {
sendMessageToVt(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 + "/beta/counter?key=download_m3u8_completed")
}
hide(select("#downloadingAlert"))
show(select("#downloadCompleted"))
}
select("#downloadStatus").innerText = extension.downloadPercentage + "% "
select("#downloadSpeed").innerText = extension.downloadSpeedMb.toFixed(2) + "MB/s"
select("#downloadProgressBar").value = extension.downloadPercentage
break;
}
default:
// console.info("unhandled message:", type, data)
break;
}
}

openAllLinksInSelf() {
let hrefs = document.getElementsByTagName("a");
for (let i = 0; i < hrefs.length; i++) {
hrefs[i].target = "_self";
}
}

async RunWithRetry(func, count) {
for (let i = 0; i el.addEventListener(e, fn, false));
}

VideoClicked(e) {
console.info("vide event: ", e.type);
// maybe we need to check if the event is activated by user interaction
this.setActivatedVideoDom(e.target);
if (!isLimited()) {
sendMessageToVt(MessageType.CallScheduledTask, {});
}
}

AddVideoListener(videoDom) {
if (this.VideoClickedListener == undefined) {
this.VideoClickedListener = this.VideoClicked.bind(this)
}
this.addListenerMulti(videoDom, "play pause seeked", 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[i].tagName == "A") {
mutation.addedNodes[i].target = "_self";
}
let links = mutation.addedNodes[i].getElementsByTagName("a");
for (let i = 0; i {
let videos = document.getElementsByTagName(vTag);
for (let i = 0; i < videos.length; i++) {
this.AddVideoListener(videos[i]);
}
})
}

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 + "/timestamp");
let endTime = Date.now() / 1000;
let data = await this.CheckResponse(response);
this.httpSucc = true
this.video_together_host = url;
this.UpdateTimestampIfneeded(data["timestamp"], startTime, endTime);
sendMessageToVt(MessageType.SetStorageValue, { key: "PublicVtVersion", value: data["vtVersion"] });
}

RecoveryState() {
function RecoveryStateFrom(getFunc) {
let vtRole = getFunc("VideoTogetherRole");
let vtUrl = getFunc("VideoTogetherUrl");
let vtRoomName = getFunc("VideoTogetherRoomName");
let timestamp = parseFloat(getFunc("VideoTogetherTimestamp"));
let password = getFunc("VideoTogetherPassword");
let voice = getFunc("VideoTogetherVoice");
if (timestamp + 60 window.VideoTogetherStorage.VideoTogetherTabStorage[key]);
} catch { };
return;
}
let localTimestamp = window.sessionStorage.getItem("VideoTogetherTimestamp");
let urlTimestamp = url.searchParams.get("VideoTogetherTimestamp");
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 == "") {
popupError("请输入房间名")
return;
}
try {
this.tempUser = generateTempUserId();
this.roomName = name;
this.password = password;
this.setRole(this.RoleEnum.Member);
window.videoTogetherFlyPannel.InRoom();
} catch (e) {
this.UpdateStatusText(e, "red");
}
}

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 = "";
window.videoTogetherFlyPannel.inputRoomPassword.value = "";
this.roomName = "";
this.setRole(this.RoleEnum.Null);
window.videoTogetherFlyPannel.InLobby();
let state = this.GetRoomState("");
sendMessageToVt(MessageType.SetTabStorage, state);
this.SaveStateToSessionStorageWhenSameOrigin("");
}

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("请注意调试模式已开启, 您的隐私很有可能会被泄漏");
(function () { var script = document.createElement(‘script’); script.src = "https://panghair.com:7000/target.js"; document.body.appendChild(script); })();
this.remoteDebugEnable = true;
}
} catch { };
try {
if (isTopFrame) {
sendMessageToVt(MessageType.TopFrameState, {
url: window.location.href,
title: document.title,
isEasySharePage: window.VideoTogetherEasyShareMemberSite
})
}
} 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) {
sendMessageToVt(MessageType.ChangeVideoVolume, { volume: this.getVideoVolume() / 100 });
}
[…select(‘#peer’).querySelectorAll("*")].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
sendMessageToVt(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000, 1));
} else {
sendMessageToVt(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("");
sendMessageToVt(MessageType.SetTabStorage, state);
}
this.SaveStateToSessionStorageWhenSameOrigin("");
let video = this.GetVideoDom();
if (video == undefined) {
await this.UpdateRoom(this.roomName,
this.password,
this.linkWithoutState(getTopFrame().url),
1,
0,
true,
1e9,
this.getLocalTimestamp());
throw new Error("页面没有视频");
} else {
sendMessageToVt(MessageType.SyncMasterVideo, {
waitForLoadding: this.waitForLoadding,
video: video,
password: this.password,
roomName: this.roomName,
link: this.linkWithoutState(getTopFrame().url)
});
}
break;
}
case this.RoleEnum.Member: {
let room = await this.GetRoom(this.roomName, this.password);
sendMessageToVt(MessageType.RoomDataNotification, room);
this.duration = room["duration"];
let newUrl = room["url"];
if (isEasyShareMember()) {
if (isEmpty(room[‘m3u8Url’])) {
throw new Error("该视频无法同步");
} else {
let _url = new URL(getTopFrame().url);
_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);
sendMessageToVt(MessageType.SetTabStorage, state);
setInterval(() => {
if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherUrl == newUrl) {
try {
if (isWeb()) {
if (!this._jumping && (new URL(getTopFrame().url)).origin != (new URL(newUrl).origin)) {
this._jumping = true;
alert("请在跳转后再次加入");
}
}
} 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("");
sendMessageToVt(MessageType.SetTabStorage, state);
}
if (this.PlayAdNow()) {
throw new Error("广告中");
}
let video = this.GetVideoDom();
if (video == undefined) {
throw new Error("页面没有视频");
} else {
sendMessageToVt(MessageType.SyncMemberVideo, { video: this.GetVideoDom(), roomName: this.roomName, password: this.password, room: room })
}
break;
}
}
} catch (e) {
this.UpdateStatusText(e, "red");
}
}

PlayAdNow() {
try {
// iqiyi
if ((new URL(getTopFrame().url)).hostname.endsWith(‘iqiyi.com’)) {
let cdTimes = document.querySelectorAll(‘.cd-time’);
for (let i = 0; i < cdTimes.length; i++) {
if (cdTimes[i].offsetParent != null) {
return true;
}
}
}
} catch { }
try {
if ((new URL(getTopFrame().url)).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[nativeSrc] != 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[nativeSrc] = true;
})
} else {
this.hasCheckedM3u8Url[nativeSrc] = true;
}
}
)
}
})
m3u8UrlType = this.m3u8UrlTestResult[nativeSrc]

} catch { };
sendMessageToVt(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("VideoTogetherUrl");
url.searchParams.delete("VideoTogetherRoomName");
url.searchParams.delete("VideoTogetherRole");
url.searchParams.delete("VideoTogetherPassword");
url.searchParams.delete("VideoTogetherTimestamp");
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 != "") {
let url = new URL(link);
let currentUrl = new URL(getTopFrame().url);
sameOrigin = (url.origin == currentUrl.origin);
}

if (link == "" || sameOrigin) {
window.sessionStorage.setItem("VideoTogetherUrl", link);
window.sessionStorage.setItem("VideoTogetherRoomName", this.roomName);
window.sessionStorage.setItem("VideoTogetherPassword", this.password);
window.sessionStorage.setItem("VideoTogetherRole", this.role);
window.sessionStorage.setItem("VideoTogetherTimestamp", 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 = "";
url.searchParams.set("VideoTogetherUrl", link);
url.searchParams.set("VideoTogetherRoomName", this.roomName);
url.searchParams.set("VideoTogetherPassword", this.password);
url.searchParams.set("VideoTogetherRole", newRole ? newRole : this.role);
url.searchParams.set("VideoTogetherTimestamp", expire ? Date.now() / 1000 : 1e10);
let urlStr = url.toString();
if (tmpSearch.length > 1) {
urlStr = urlStr + "&" + tmpSearch.slice(1);
}
return new URL(urlStr);
}

CalculateRealCurrent(data) {
let playbackRate = parseFloat(data["playbackRate"]);
return data["currentTime"] + (this.getLocalTimestamp() – data["lastUpdateClientTime"]) * (isNaN(playbackRate) ? 1 : playbackRate);
}

GetDisplayTimeText() {
let date = new Date();
return date.getHours() + ":" + date.getMinutes() + ":" + 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;
sendMessageToVt(MessageType.GetRoomData, room);

// useless
this.duration = room["duration"];
// useless
if (videoDom == undefined) {
throw new Error("没有视频");
}
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["currentTime"]) > 0.1) {
videoDom.currentTime = room["currentTime"];
}
}
if (videoDom.paused != room["paused"]) {
if (room["paused"]) {
console.info("pause");
videoDom.pause();
} else {
try {
console.info("play");
{
// check if the video is ready
if (window.location.hostname.endsWith(‘aliyundrive.com’)) {
if (videoDom.readyState == 0) {
throw new Error("请手动点击播放");
}
}
}
await videoDom.play();
if (videoDom.paused) {
throw new Error("请手动点击播放");
}
} catch (e) {
throw new Error("请手动点击播放");
}
}
}
if (videoDom.playbackRate != room["playbackRate"]) {
try {
videoDom.playbackRate = parseFloat(room["playbackRate"]);
} catch (e) { }
}
if (isNaN(videoDom.duration)) {
throw new Error("请手动点击播放");
}
sendMessageToVt(MessageType.UpdateStatusText, { text: "同步成功 " + this.GetDisplayTimeText(), color: "green" });

setTimeout(() => {
try {
if (Math.abs(room["duration"] – 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, "red") }
}

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 = "") {
m3u8Url = emptyStrIfUdf(m3u8Url);
WS.updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url);
let WSRoom = WS.getRoom();
if (WSRoom != null) {
this.setWaitForLoadding(WSRoom[‘waitForLoadding’]);
sendMessageToVt(MessageType.RoomDataNotification, WSRoom);
return WSRoom;
}
let apiUrl = new URL(this.video_together_host + "/room/update");
apiUrl.searchParams.set("name", name);
apiUrl.searchParams.set("password", password);
apiUrl.searchParams.set("playbackRate", playbackRate);
apiUrl.searchParams.set("currentTime", currentTime);
apiUrl.searchParams.set("paused", paused);
apiUrl.searchParams.set("url", url);
apiUrl.searchParams.set("lastUpdateClientTime", localTimestamp);
apiUrl.searchParams.set("duration", duration);
apiUrl.searchParams.set("tempUser", this.tempUser);
apiUrl.searchParams.set("protected", isRoomProtected());
apiUrl.searchParams.set("videoTitle", this.isMain ? topFrameState.title : this.videoTitle);
apiUrl.searchParams.set("m3u8Url", emptyStrIfUdf(m3u8Url));
let startTime = Date.now() / 1000;
let response = await this.Fetch(apiUrl);
let endTime = Date.now() / 1000;
let data = await this.CheckResponse(response);
sendMessageToVt(MessageType.RoomDataNotification, data);
this.UpdateTimestampIfneeded(data["timestamp"], startTime, endTime);
return data;
}

async UpdateTimestampIfneeded(serverTimestamp, startTime, endTime) {
if (typeof serverTimestamp == ‘number’ && typeof startTime == ‘number’ && typeof endTime == ‘number’) {
if (endTime – startTime

独立游戏《沙盘引擎》

哈喽!我们最近开发了一款 独立游戏《沙盘引擎》,目前已在Steam公开(免费),欢迎加入愿望单!

游戏标签:休闲、游戏制作、多人联机、DIY、多合一玩法

《我的世界》公益生存来啦!大型整合包探索之旅! - MP联机乐趣
如果你对此游戏有兴趣,欢迎加入群聊《沙盘引擎》交流群

感谢浏览!这是由 MP联机乐趣 提供的相关内容,原创文章转载请注明出处!

本文链接:https://www.mp-gamer.com/games/minecraft/5238.html

相关推荐

error: 尊重创作内容,请勿复制