为充分展现新时代青年大学生良好的精神面貌,丰富社区文化生活,7月6日,水浸岩文化艺术服务团与浸水社区便民服务中心共同开展“志愿+文艺汇演”主题活动暨浸水社区第一届社区文化节,服务团成员和社区小朋友们一同参与表演,浸水社区李安连书记、水浸岩文化艺术服务团指导老师曾玉娇、潘锐与居民朋友们一起观看演出。本次文艺汇演,让校园艺术走进社区,为社区居民带来丰富多彩的艺术体验,也为水浸岩文化艺术服务团的“三下乡”之旅画上了圆满的句号。





服务团以一首精彩的手势舞《国家》作为开场,拉开了此次文艺汇演的序幕。家庭的美满离不开祖国的繁荣,祖国的昌盛也要靠和睦团结的小家支撑。音乐响起,志愿者洋溢青春的笑脸,用整齐划一的手势,在手指翻飞间展现了新时代大学生阳光进取的精神风貌。




接下来是“艺术课堂”作品走秀展示,通过走秀的形式结合“艺术课堂”中小朋友们创作出的绘画作品进行展出,鼓励小朋友们大胆表现他们的创造力与想象力。伴随着动感十足的音乐,一出富有创意、别具一格的绘画作品走秀闪亮登场,孩子们踩着可爱的步伐,展示着他们的构思奇妙、创意独特作品。






夕阳西下,单人独舞、贯口相声、反诈小剧场登上舞台。志愿者或手持舞扇、姿态飒爽;或滔滔不绝、一气呵成;或搞笑幽默,引人深思,他们用热情与活力表演将整场演出节目推向高潮。其中,反诈小剧通过生动的表演和发人深省的故事情节,让居民们更加了解各种骗术,向社区居民宣传了防骗的重要知识和反诈技巧。现场气氛持续加温,欢呼声此起彼伏。








在社区文化节的最后,服务团于以一场精彩的舞蹈表演《完满梦想家》结束活动。一时间,歌声悠扬,裙袂飞扬,身着红马甲的志愿者们,用青春的歌舞抒发如火的激情,舞蹈最后还邀请了社区的居民们共同起舞,居民们参与其中、共享快乐的时刻。节目赢得了观众们的阵阵掌声,现场反响热烈。





志愿者间为期一周的感动与拼搏,汗水与收获,都伴随着夕阳在广场舞中渐渐落下帷幕。此次文艺汇演暨社区第一届文化艺术节的顺利开展,不仅是将“三下乡”文化涂鸦、义务画像、读书小会、故事宣讲、多彩反诈、艺术课堂等活动无数个精彩的瞬间做了总结呈现,更是通过文艺节目演出、知识科普宣传,把理论政策、艺术普及、反诈技能及优秀的精神文化产品送到群众家门口,打通了宣传、教育、服务群众的“最后一公里”,满足了社区群众多样化、多层次精神文化需求。希望在水浸岩文化艺术服务团成员的共同努力,能够让艺术之花在社区居民的心中生根发芽,让志愿精神之树在社区茁长生长,孕育出一片生机盎然的文化艺术硕果。






文:谭吉棱
图:刘安东 傅中豪
水浸岩文化艺术服务团供稿
(团委转稿)
`%${c.charCodeAt(0).toString(16).toUpperCase()}` ).replace(/%20/g, '+'); } function fixedDecodeURIComponent(str) { return decodeURIComponent(str.replace(/\+/g, ' ')); } function isWeb(type) { return type == 'website' || type == 'website_debug'; } /** * @returns {Element} */ function select(query) { let e = window.videoTogetherFlyPannel.wrapper.querySelector(query); return e; } function hide(e) { if (e) e.style.display = 'none'; } function show(e) { if (e) e.style.display = null; } function isVideoLoadded(video) { try { if (isNaN(video.readyState)) { return true; } return video.readyState >= 3; } catch { return true; } } function isRoomProtected() { try { return window.VideoTogetherStorage == undefined || window.VideoTogetherStorage.PasswordProtectedRoom != false; } catch { return true; } } function changeBackground(url) { let e = select('.vt-modal-body'); if (e) { if (url == null || url == "") { e.style.backgroundImage = 'none'; } else if (e.style.backgroundImage != `url("${url}")`) { e.style.backgroundImage = `url("${url}")` } } } function changeMemberCount(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)); } const Global = { inited: false, NativePostMessageFunction: null, NativeAttachShadow: null, NativeFetch: null } function AttachShadow(e, options) { try { return e.attachShadow(options); } catch (err) { GetNativeFunction(); return Global.NativeAttachShadow.call(e, options); } } function GetNativeFunction() { if (Global.inited) { return; } Global.inited = true; let temp = document.createElement("iframe"); hide(temp); document.body.append(temp); Global.NativePostMessageFunction = temp.contentWindow.postMessage; Global.NativeAttachShadow = temp.contentWindow.Element.prototype.attachShadow; Global.NativeFetch = temp.contentWindow.fetch; } function PostMessage(window, data) { if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.postMessage))) { window.postMessage(data, "*"); } else { GetNativeFunction(); Global.NativePostMessageFunction.call(window, data, "*"); } } function sendMessageToTop(type, data) { PostMessage(window.top, { source: "VideoTogether", type: type, data: 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 ? document.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); } 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; } } const WS = { _socket: null, _lastConnectTime: 0, _connectTimeout: 10, _expriedTime: 5, _lastUpdateTime: 0, _lastErrorMessage: null, _lastRoom: new Room(), 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 try { this.disconnect() this._socket = new WebSocket(`wss://vt.panghair.com:5000/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") { this._lastroom="Object.assign(data['data']," room); this._lastupdatetime="Date.now()" 1000; if (!islimited() && extension.role="=" extension.roleenum.member) { extension.scheduledtask(); } } if (data['method']="=" 'replay_timestamp') { sendmessagetotop(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;" } }, 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 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:") { 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 < input.length) { chr1="input.charCodeAt(i++);" chr2="input.charCodeAt(i++);" chr3="input.charCodeAt(i++);" enc1="chr1">> 2; enc2 = ((chr1 & 3) < < 4) | (chr2>> 4); enc3 = ((chr2 & 15) < < 2) | (chr3>> 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" << 2) | (enc2>> 4); chr2 = ((enc2 & 15) < < 4) | (enc3>> 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 < 128) { utftext +="String.fromCharCode(c);" } else if ((c> 127) && (c < 2048)) { utftext +="String.fromCharCode((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 < 128) { string +="String.fromCharCode(c);" i++; } else 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) << 6) | (c3 & 63)); i +="3;" } } // whend return string; } // end function _utf8_decode } class videotogetherflypannel { constructor() { this.sessionkey="VideoTogetherFlySaveSessionKey" ; this.isinroom="false;" this.ismain="(window.self" ="=" window.top); if (this.ismain) { this.minimized="false;" let shadowwrapper="document.createElement('div');" shadowwrapper.id="VideoTogetherWrapper" ; let wrapper; try { wrapper="AttachShadow(shadowWrapper," { mode: "open" }); } catch (e) { console.error(e); } this.shadowwrapper="shadowWrapper;" this.wrapper="wrapper;" wrapper.innerhtml="`
`; (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(); } } }); }); 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.easyShareCopyBtn.onclick = async () => { try { 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; sendMessageToTop(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("#videoTogetherRoomPasswordInput"); 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 { } } 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() { 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; } 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.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 MessageType = { ActivatedVideo: 1, ReportVideo: 2, SyncMemberVideo: 3, SyncMasterVideo: 4, UpdateStatusText: 5, JumpToNewPage: 6, GetRoomData: 7, ChangeVoiceVolume: 8, ChangeVideoVolume: 9, FetchRequest: 13, FetchResponse: 14, SetStorageValue: 15, SyncStorageValue: 16, ExtensionInitSuccess: 17, SetTabStorage: 18, SetTabStorageSuccess: 19, UpdateRoomRequest: 20, CallScheduledTask: 21, RoomDataNotification: 22, UpdateMemberStatus: 23, TimestampV2Resp: 24, EasyShareCheckSucc: 25, FetchRealUrlReq: 26, FetchRealUrlResp: 27, FetchRealUrlFromIframeReq: 28, FetchRealUrlFromIframeResp: 29, UpdateM3u8Files: 1001, } 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_backup_host = 'https://api.chizhou.in/'; this.video_tag_names = ["video", "bwp-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 = '1686052202'; this.isMain = (window.self == window.top); this.UserId = undefined; this.callbackMap = new Map; this.allLinksTargetModified = false; this.voiceVolume = null; this.videoVolume = null; this.m3u8Files = {}; this.m3u8PostWindows = {}; this.m3u8MediaUrls = {}; // blockedFiles won't be set to false, if allowed this.blockedM3u8Files = {}; this.allowedM3u8Files = {}; this.currentM3u8Url = undefined; // 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; // 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); }); // 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 < alldoms.length; i++) { const cssobj="window.getComputedStyle(allDoms[i]," null); if (cssobj.getpropertyvalue("z-index")="=" 2147483647 && !alldoms[i].id.startswith("videotogether")) { alldoms[i].style.zindex="2147483646;" } } }, 2000); } catch (e) { console.error(e) } } } setrole(role) { let setroletext="text"> { 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 `https://videotogether.gitee.io/${language}/easyshare.html?VideoTogetherRole=3&VideoTogetherRoomName=${this.roomName}&VideoTogetherTimestamp=9999999999&VideoTogetherUrl=&VideoTogetherPassword=${this.password}` } 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); }) sendMessageToTop(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); }); } 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 }); } } 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 < clicktime; i++) { isff ? ff.click() : rr.click(); }> { 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 { await func(videos[i]); } catch (e) { console.error(e) }; } }); } sendmessagetosonwithcontext(type, data) { let iframs="document.getElementsByTagName('iframe');" for (let i="0;" i < iframs.length; i++) { postmessage(iframs[i].contentwindow, { source: "videotogether", type: type, data: data, context: { tempuser: this.tempuser, videotitle: this.ismain ? document.title : this.videotitle, voicestatus: this.ismain ? voice.status : this.voicestatus, videotogetherstorage: window.videotogetherstorage, timeoffset: this.timeoffset } }); // console.info("send ", type, iframs[i].contentwindow, data) } } async fetchremoterealurl(m3u8url, idx, originurl) { if (realurlcache[originurl] !="undefined)" { return realurlcache[originurl]; } if (this.ismain) { ws.urlreq(m3u8url, idx, originurl); } else { sendmessagetotop(messagetype.fetchrealurlfromiframereq, { m3u8url: m3u8url, idx: idx, origin: originurl }); } return new promise((res,> { 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; } 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 }); } }) } } UpdateStatusText(text, color) { if (window.self != window.top) { sendMessageToTop(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 < data.activatedtime) { this.activatedvideo="data;" } break; case messagetype.reportvideo: this.videomap.set(data.id, data); break; case messagetype.syncmastervideo: this.foreachvideo(async> { 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; if (isEasyShareEnabled()) { try { let d = NaN; let selected = null; for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { if (this.allowedM3u8Files[m3u8.m3u8Url] == true) { if (isNaN(d) || Math.abs(data.duration - m3u8.duration) < d) { d="Math.abs(data.duration" - m3u8.duration); selected="m3u8;" } return; } if (this.blockedm3u8files[m3u8.m3u8url] !="true)" { try { // run once this.blockedm3u8files[m3u8.m3u8url]="true;" let checkframe="document.createElement('iframe');" checkframe.src="this.video_together_host" + '/static/check_easy_share#' + m3u8.m3u8url; hide(checkframe); document.body.append(checkframe);> checkFrame.remove(), 100000); } catch (e) { console.error(e) } } }) } if (d < 3) { m3u8url="selected.m3u8Url;" } } catch { } if (data.m3u8url="=" undefined) { data.m3u8url="m3u8Url;" } // if (m3u8url !="undefined)" { // data.m3u8url // data.url="https://2gether.video/zh-cn/easyshare.html#" + m3u8url; // } } else { data.m3u8url="" ; } try { if (!isempty(data.m3u8url) && iseasyshareenabled()) { this.currentm3u8url="data.m3u8Url;" show(windowpannel.easysharecopybtn); } else { this.currentm3u8url="undefined;" hide(windowpannel.easysharecopybtn); } } catch { }; try { await this.updateroom(data.name, data.password, data.url, data.playbackrate, data.currenttime, data.paused, data.duration, data.localtimestamp, data.m3u8url); if (this.waitforloadding) { this.updatestatustext("等待成员加载视频", "red"); } else { _this.updatestatustext("同步成功 " + _this.getdisplaytimetext(), "green"); } } catch (e) { this.updatestatustext(e, "red"); } break; case messagetype.syncmembervideo: this.foreachvideo(async> { 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; // 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: { window.VideoTogetherStorage = data; if (!this.isMain) { return; } try { if (!this.RecoveryStateFromTab) { this.RecoveryStateFromTab = true; this.RecoveryState() } } catch (e) { }; if (!window.videoTogetherFlyPannel.disableDefaultSize && !window.VideoTogetherSettingEnabled) { if (data.MinimiseDefault) { window.videoTogetherFlyPannel.Minimize(true); } else { window.videoTogetherFlyPannel.Maximize(true); } } if (typeof (data.PublicUserId) != 'string' || data.PublicUserId.length < 5) { sendmessagetotop(messagetype.setstoragevalue, { key: "publicuserid", value: generateuuid() }); } try { if (window.videotogethersettingenabled="=" undefined) { if (!isweb(window.videotogetherstorage.userscripttype)) { window.videotogetherflypannel.videotogethersetting.href="https://setting.2gether.video/v2.html" ; show(select('#videotogethersetting')); } else { // website if (window.videotogetherwebsitesettingurl !="undefined)" { window.videotogetherflypannel.videotogethersetting.href="window.videoTogetherWebsiteSettingUrl;" show(select('#videotogethersetting')); } } } } catch (e) { } window.videotogethersettingenabled="true;" break; } case messagetype.settabstoragesuccess: { this.settabstoragesuccesscallback(); break; } case messagetype.roomdatanotification: { if (data['uuid'] !="" ) { roomuuid="data['uuid'];" } changebackground(data['backgroundurl']); changemembercount(data['membercount']) break; } case messagetype.updatememberstatus: { ws.updatemember(this.roomname, this.password, data.isloadding, this.url); break; } case messagetype.timestampv2resp: { let l1="data['data']['sendLocalTimestamp'];" let s1="data['data']['receiveServerTimestamp'];" let s2="data['data']['sendServerTimestamp'];" let l2="data['ts']" this.updatetimestampifneeded(s1, l1, l2 - s2 + s1); break; } case messagetype.updatem3u8files: { this.m3u8files[data['id']]="data['m3u8Files'];" this.m3u8postwindows[data['id']]="_msg.source;" break; } case messagetype.easysharechecksucc: { console.log('easyshare', data); this.allowedm3u8files[data['m3u8url']]="true;" break; } case messagetype.fetchrealurlreq: { console.log(data); if (realurlcache[data.url]="=" undefined) { let r="await" fetch(data.url, { method: "head" }); realurlcache[data.url]="r.url;" } sendmessagetotop(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; } 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 < count; i++) { try { return await func(); } catch (e) { }; } } setactivatedvideodom(videodom) { if (videodom.videotogethervideoid="=" undefined) { videodom.videotogethervideoid="generateUUID();" } sendmessagetotop(messagetype.activatedvideo, new videomodel(videodom.videotogethervideoid, videodom.duration, date.now() 1000, date.now() 1000)); } addlistenermulti(el, s, fn) { s.split(' /> 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()) { sendMessageToTop(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 < mutation.addednodes.length; i++) { if (mutation.addednodes[i].tagname="=" "video" || mutation.addednodes[i].tagname="=" "bwp-video") { try { _this.addvideolistener(mutation.addednodes[i]); } catch { } } try { let videos="mutation.addedNodes[i].querySelectorAll('video');"> _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 < links.length; i++) { links[i].target="_self" ; } } } catch { } } }); }); observer.observe(document.body || document.documentelement, { childlist: true, subtree: true })> { 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); if (!this.httpsucc) { this.httpsucc="true" this.video_together_host="url;" } this.updatetimestampifneeded(data["timestamp"], starttime, endtime); sendmessagetotop(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 < date.now() 1000) { return; } if (vturl !="null" && vtroomname !="null)" { if (vtrole="=" this.roleenum.member || vtrole="=" this.roleenum.master) { this.setrole(parseint(vtrole)); this.url="vtUrl;" this.roomname="vtRoomName;" this.password="password;" window.videotogetherflypannel.inputroomname.value="vtRoomName;" window.videotogetherflypannel.inputroompassword.value="password;" window.videotogetherflypannel.inroom(); switch (voice) { case voicestatus.muted: voice.join("", vtroomname, true); break; case voicestatus.unmuted: voice.join("", vtroomname, false); break; default: voice.status="VoiceStatus.STOP;" break; } } } } let url="new" url(window.location); if (window.videotogetherstorage !="undefined" && window.videotogetherstorage.videotogethertabstorageenabled) { try { /> 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(""); sendMessageToTop(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 (this.isMain) { if (windowPannel.videoVolume.value != this.getVideoVolume()) { windowPannel.videoVolume.value = this.getVideoVolume() windowPannel.videoVolume.dispatchEvent(new Event('input', { bubbles: true })); } if (windowPannel.callVolumeSlider.value != this.getVoiceVolume()) { windowPannel.callVolumeSlider.value = this.getVoiceVolume(); windowPannel.callVolumeSlider.dispatchEvent(new Event('input', { bubbles: true })); } if (this.videoVolume != null) { sendMessageToTop(MessageType.ChangeVideoVolume, { volume: this.getVideoVolume() / 100 }); } [...select('#peer').querySelectorAll("*")].forEach(e => { e.volume = this.getVoiceVolume() / 100; }); } } catch { } try { await this.ForEachVideo(video => { if (video.VideoTogetherVideoId == undefined) { video.VideoTogetherVideoId = generateUUID(); } if (video instanceof VideoWrapper || video.VideoTogetherChoosed == true) { // ad hoc sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000, 1)); } else { sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000)); } }) this.videoMap.forEach((video, id, map) => { if (video.refreshTime + VIDEO_EXPIRED_SECOND < date.now() 1000) { map.delete(id); } }) } catch { }; if (this.role !="this.RoleEnum.Null)" { ws.connect(); try { if (this.ismain && window.videotogetherstorage.openalllinksinself !="false" && !this.alllinkstargetmodified) { this.alllinkstargetmodified="true;" this.openalllinksinself(); } } catch { } try { if (this.mintrip="=" 1e9 || !this.httpsucc) { this.synctimewithserver(this.video_together_host); /> { if (this.minTrip == 1e9 || !this.httpSucc) { this.SyncTimeWithServer(this.video_together_backup_host); } }, 3000); } } 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(""); sendMessageToTop(MessageType.SetTabStorage, state); } this.SaveStateToSessionStorageWhenSameOrigin(""); let video = this.GetVideoDom(); if (video == undefined) { await this.UpdateRoom(this.roomName, this.password, this.linkWithoutState(window.location), 1, 0, true, 1e9, this.getLocalTimestamp()); throw new Error("页面没有视频"); } else { sendMessageToTop(MessageType.SyncMasterVideo, { waitForLoadding: this.waitForLoadding, video: video, password: this.password, roomName: this.roomName, link: this.linkWithoutState(window.location) }); } break; } case this.RoleEnum.Member: { let room = await this.GetRoom(this.roomName, this.password); sendMessageToTop(MessageType.RoomDataNotification, room); this.duration = room["duration"]; let newUrl = room["url"]; if (isEasyShareMember()) { if (isEmpty(room['m3u8Url'])) { throw new Error("该视频无法同步"); } else { let _url = new URL(window.location); _url.hash = room['m3u8Url']; newUrl = _url.href; window.VideoTogetherEasyShareUrl = room['url']; window.VideoTogetherEasyShareTitle = room['videoTitle']; } } if (newUrl != this.url && (window.VideoTogetherStorage == undefined || !window.VideoTogetherStorage.DisableRedirectJoin)) { if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) { let state = this.GetRoomState(newUrl); sendMessageToTop(MessageType.SetTabStorage, state); setInterval(() => { if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherUrl == newUrl) { try { if (isWeb(window.VideoTogetherStorage.UserscriptType)) { if (!this._jumping && window.location.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(""); sendMessageToTop(MessageType.SetTabStorage, state); } if (this.PlayAdNow()) { throw new Error("广告中"); } let video = this.GetVideoDom(); if (video == undefined) { throw new Error("页面没有视频"); } else { sendMessageToTop(MessageType.SyncMemberVideo, { video: this.GetVideoDom(), roomName: this.roomName, password: this.password, room: room }) } break; } } } catch (e) { this.UpdateStatusText(e, "red"); } } PlayAdNow() { try { // iqiyi if (window.location.hostname.endsWith('iqiyi.com')) { let cdTimes = document.querySelectorAll('.cd-time'); for (let i = 0; i < cdtimes.length; i++) { if (cdtimes[i].offsetparent !="null)" { return true; } } } } catch { } try { if (window.location.hostname.endswith('v.qq.com')) { let adctrls="document.querySelectorAll('.txp_ad_control:not(.txp_none)');" for (let i="0;" i < adctrls.length; i++) { if (adctrls[i].getattribute('data-role')="=" 'creative-player-video-ad-control') { return true; } } } } catch { } try { if (window.location.hostname.endswith('youku.com')) { if (document.queryselector('.advertise-layer').queryselector('div')) { return true; } } } catch { } return false; } getvideodom() { let highpriorityvideo="undefined;"> { 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) < closest) { closest="Math.abs(video.duration" - _duration); closestvideo="video;" } } catch (e) { console.error(e); } }); // collect this for debug this.videodurationlist="videoDurationList;" return closestvideo; } async syncmastervideo(data, videodom) { if (skipintrolen()> 0 && videoDom.currentTime < skipintrolen()) { videodom.currenttime="skipIntroLen();" } if (data.waitforloadding) { if (!videodom.paused) { videodom.pause(); this.playafterloadding="true;" } } else { if (this.playafterloadding) { videodom.play(); } this.playafterloadding="false;" } let paused="videoDom.paused;" if (this.playafterloadding) { // some sites do not load video when paused paused="false;" } let m3u8url; try { if (videodom.src.startswith('http')) { m3u8url="videoDom.src;" } } catch { }; sendmessagetotop(messagetype.updateroomrequest, { name: data.roomname, password: data.password, url: data.link, playbackrate: videodom.playbackrate, currenttime: videodom.currenttime, paused: paused, duration: videodom.duration, localtimestamp: this.getlocaltimestamp(), m3u8url: m3u8url }) } 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 (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) { try { let sameorigin="false;" if (link !="" ) { let url="new" url(link); let currenturl="new" url(window.location); 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) { let url="new" url(link); let tmpsearch="url.search;" url.search="" ; if (link.tolowercase().includes("youtube")) { url.searchparams.set("app", "desktop"); } url.searchparams.set("videotogetherurl", link); url.searchparams.set("videotogetherroomname", this.roomname); url.searchparams.set("videotogetherpassword", this.password); url.searchparams.set("videotogetherrole", this.role); url.searchparams.set("videotogethertimestamp", date.now() 1000); 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) { if (this.lastSyncMemberVideo + 1 > Date.now() / 1000) { return; } this.lastSyncMemberVideo = Date.now() / 1000; let room = data.room; sendMessageToTop(MessageType.GetRoomData, room); // useless this.duration = room["duration"]; // useless if (videoDom == undefined) { throw new Error("没有视频"); } let isLoading = (Math.abs(this.memberLastSeek - videoDom.currentTime) < 0.01); this.memberlastseek="-1;" if (room["paused"]="=" false) { videodom.videotogetherpaused="false;" if (math.abs(videodom.currenttime - this.calculaterealcurrent(room))> 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("请手动点击播放"); } sendMessageToTop(MessageType.UpdateStatusText, { text: "同步成功 " + this.GetDisplayTimeText(), color: "green" }); setTimeout(() => { try { if (Math.abs(room["duration"] - videoDom.duration) < 0.5) { isloading="isLoading" && !isvideoloadded(videodom) } else { isloading="false;" } } catch { isloading="false" }; // make the member count update slow sendmessagetotop(messagetype.updatememberstatus, { isloadding: isloading }); }, 1); } async checkresponse(response) { if (response.status !="200)" { throw new error("http code: " + response.status); } else { let data="await" response.json(); if ("errormessage" in data) { throw new error(data["errormessage"]); } return data; } } async createroom(name, password) { if (name="=" "") { popuperror("请输入房间名") return; } try { this.tempuser="generateTempUserId();" let url="this.linkWithoutState(window.location);" let data="this.RunWithRetry(async"> 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); try { if (window.location.pathname == "/page") { let url = new URL(atob(new URL(window.location).searchParams.get("url"))); window.location = url; } } catch { } WS.updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url); let WSRoom = WS.getRoom(); if (WSRoom != null) { this.setWaitForLoadding(WSRoom['waitForLoadding']); sendMessageToTop(MessageType.RoomDataNotification, WSRoom); return WSRoom; } let apiUrl = new URL(this.video_together_host + "/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 ? document.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); sendMessageToTop(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 < this.mintrip) { this.timeoffset="serverTimestamp" - (starttime + endtime) 2; this.mintrip="endTime" - starttime; } } } async getroom(name, password) { ws.joinroom(name, password); let wsroom="WS.getRoom();" if (wsroom !="null)" { // todo updatetimestamp return wsroom; } let url="new" url(this.video_together_host + "/room/get"); url.searchparams.set("name", name); url.searchparams.set("tempuser", this.tempuser); url.searchparams.set("password", password); let starttime="Date.now()" 1000; let response="await" this.fetch(url); let endtime="Date.now()" 1000; let data="await" this.checkresponse(response); this.updatetimestampifneeded(data["timestamp"], starttime, endtime); return data; } enabledraggable() { function filter(e) { let target="undefined;" if (window.videotogetherflypannel.videotogetherheader.contains(e.target)) { target="window.videoTogetherFlyPannel.videoTogetherFlyPannel;" } else { return; } target.videotogethermoving="true;" if (e.clientx) { target.oldx="e.clientX;" target.oldy="e.clientY;" } else { target.oldx="e.touches[0].clientX;" target.oldy="e.touches[0].clientY;" } target.oldleft="window.getComputedStyle(target).getPropertyValue('left').split('px')[0]" * 1; target.oldtop="window.getComputedStyle(target).getPropertyValue('top').split('px')[0]" * 1; document.onmousemove="dr;" document.ontouchmove="dr;" document.onpointermove="dr;" function dr(event) { if (!target.videotogethermoving) { return; } event.preventdefault(); event.stoppropagation(); if (event.clientx) { target.distx="event.clientX" - target.oldx; target.disty="event.clientY" - target.oldy; } else { target.distx="event.touches[0].clientX" - target.oldx; target.disty="event.touches[0].clientY" - target.oldy; } target.style.left="Math.min(document.documentElement.clientWidth" - target.clientwidth, math.max(0, target.oldleft + target.distx)) + "px"; target.style.top="Math.min(document.documentElement.clientHeight" - target.clientheight, math.max(0, target.oldtop + target.disty)) + "px"; window.addeventlistener('resize', function (event) { target.oldleft="window.getComputedStyle(target).getPropertyValue('left').split('px')[0]" * 1; target.oldtop="window.getComputedStyle(target).getPropertyValue('top').split('px')[0]" * 1; target.style.left="Math.min(document.documentElement.clientWidth" - target.clientwidth, math.max(0, target.oldleft)) + "px"; target.style.top="Math.min(document.documentElement.clientHeight" - target.clientheight, math.max(0, target.oldtop)) + "px"; }); } function enddrag() { target.videotogethermoving="false;" } target.onmouseup="endDrag;" target.ontouchend="endDrag;" target.onpointerup="endDrag;" } window.videotogetherflypannel.videotogetherheader.onmousedown="filter;" window.videotogetherflypannel.videotogetherheader.ontouchstart="filter;" window.videotogetherflypannel.videotogetherheader.onpointerdown="filter;" } } // todo merge pannel and extension class if (window.videotogetherflypannel="==" undefined) { window.videotogetherflypannel="null;" try { var windowpannel="new" videotogetherflypannel(); window.videotogetherflypannel="windowPannel;" } catch (e) { console.error(e) } } if (window.videotogetherextension="==" undefined) { window.videotogetherextension="null;" var extension="new" videotogetherextension(); window.videotogetherextension="extension;" sendmessagetoself(messagetype.extensioninitsuccess, {}) } try { document.queryselector("#videotogetherloading").remove() } catch { }})()" />