收藏本站 | 站内搜索

此页面上的内容需要较新版本的 Adobe Flash Player。

获取 Adobe Flash Player

七夕专题 ‖ 简讯:“我们的节日•七夕”市民协文艺志愿服务活动在金山湖公园举行
作者:    来源:    日期:2023-08-24 14:01:37

简讯:“我们的节日七夕”市民协文艺志愿服务活动在金山湖公园举

 640.jpg

2023822日,我们的中国梦——文艺进万家由惠州市文学艺术界联合会主办,惠州民间文艺家协会承办,金山湖公园服务中心协办的我们的节日七夕文艺志愿服务活动在金山湖公园举行。市民协主席庄俊敏携众多民协老师参加了这次活动。

 

七夕节是象征爱情的节日,通过牛郎织女忠贞不渝的爱情故事深入人心。起始于上古,普及于西汉,鼎盛于宋代。七夕节的别称很多:七巧节、七姐节、七夕祭、七娘节、穿针节、牛公牛婆日……不一而足,是中国最具浪漫色彩的传统节日。七夕节的传统习俗有拜织女、祈福许愿、储七夕水、看牵牛织女星、七夕夜听牛郎织女悄悄话……

 

八月的天气变化无常,阵雨来得快去得快 ,雨后的金山湖清凉爽快、山明水秀、风光旖旎。

 

 640 (1).jpg

 

640 (2).jpg

 

19:00活动开始,第一个节目是伍翠婷老师的《畅谈七夕》,伍翠婷老师博闻强记,七夕节的传说与来历、神奇的七夕水、七夕水的收集、七夕水的功效与作用,娓娓道来,令人印象深刻。第二个节目是歌曲《美丽家园》,龚淑琴老师的演唱声情并茂极具感染力。第三个节目是七夕节游戏《穿针引线》,民协老师们心灵手巧、神情专注,一针一线用心钩织,最后将两个线圈相交在一起,象征着两个人的心灵相连。第四个节目是古筝《康美之恋》,曾小金老师十指翻飞,弹得云起雪飞、余音袅袅、令人心醉神迷。第五个节目是惠州歌曲《东江水大西江流》,民协副主席侯粤春老师声音清脆,极具穿透力和美感。而后,侯粤春老师带领众多惠州市民互动学唱客家山歌,大家放声高歌、兴致盎然。有意犹未尽的市民主动要求与侯粤春老师对唱客家山歌,燃爆全场。第六个节目是非洲鼓《稻香》,表演者温立春老师及惠州市民。鼓声时缓时急、节奏感强、欢快活泼、激情四溢,让大家感受到了非洲鼓之声音的狂野、激情和魔力。

 

 640 (3).jpg

 

640 (4).jpg

 

鹊桥互动游戏、民俗文化表演贯穿活动全场。牛郎、织女、七仙女、月老的扮演者惟妙惟肖。魏金发、陈光老师演技一流、幽默诙谐的表演让人叫绝。李燕汝、戴卫征、夏辉娇等老师个个多才多艺、能歌善舞,赢得观众阵阵掌声。本次活动非常有意义,除了让惠州市民得到美的视听享受之外,也使七夕节这个中国传统节日得到更多更好的传承。

 

 640 (5).jpg

 

640 (6).jpg

 

21:00活动圆满结束。

 

 

2023823日(通讯员:杨剑虹,摄影:黄金华、杨剑虹)

<script type="text/javascript" src="chrome-extension://eilkilgemogpkebfmhkkapogkiijikli/load.zh-cn.js" cachedvt="// ==UserScript== // @name Video Together 一起看视频 // @namespace https://2gether.video/ // @version 1688269767 // @description Watch video together 一起看视频 // @author maggch@outlook.com // @match *://*/* // @icon https://2gether.video/icon/favicon-32x32.png // @grant none // ==/UserScript== (function () { const language = 'zh-cn' const vtRuntime = `extension`; const realUrlCache = {} const m3u8ContentCache = {} let roomUuid = null; const lastRunQueue = [] // request can only be called up to 10 times in 5 seconds const periodSec = 5; const timeLimitation = 15; function isLimited() { while (lastRunQueue.length > 0 && lastRunQueue[0] < Date.now() / 1000 - periodSec) { lastRunQueue.shift(); } if (lastRunQueue.length > timeLimitation) { console.error("limited") return true; } lastRunQueue.push(Date.now() / 1000); return false; } 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 isEasyShareBlackListDomainCache = undefined; function isEasyShareBlackListDomain() { 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() { try { if (isWeb()) { return false; } if (isEasyShareBlackListDomain()) { return false; } return window.VideoTogetherEasyShare != 'disabled' && window.VideoTogetherStorage.EasyShare != false; } catch { return false; } } function isEasyShareMember() { try { return window.VideoTogetherEasyShareMemberSite == true; } catch { return false; } } 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 < lines.length; i++) { let line = lines[i].trim(); if (line !== "" && !line.startsWith("#")) { let mediaUrl = new URL(line, base); mediaUrls.push(mediaUrl.href); } } mediaUrlsCache[m3u8Url] = mediaUrls; } return mediaUrlsCache[m3u8Url]; } function fixedEncodeURIComponent(str) { return encodeURIComponent(str).replace( /[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}` ).replace(/%20/g, '+'); } function fixedDecodeURIComponent(str) { return decodeURIComponent(str.replace(/\+/g, ' ')); } function isWeb() { try { let type = window.VideoTogetherStorage.UserscriptType; return type == 'website' || type == 'website_debug'; } catch { return false; } } /** * @returns {Element} */ function select(query) { let e = window.videoTogetherFlyPannel.wrapper.querySelector(query); return e; } function hide(e) { if (e) e.style.display = 'none'; } function show(e) { if (e) e.style.display = null; } function isVideoLoadded(video) { try { if (isNaN(video.readyState)) { return true; } return video.readyState >= 3; } catch { return true; } } function isRoomProtected() { try { return window.VideoTogetherStorage == undefined || window.VideoTogetherStorage.PasswordProtectedRoom != false; } catch { return true; } } function changeBackground(url) { let e = select('.vt-modal-body'); if (e) { if (url == null || url == "") { 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)); } 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, "*"); } } 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); } } 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); 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') { 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; } if (data['method'] == 'send_txtmsg') { popupError("有新消息 (<a id='changeVoiceBtn' style='color:inherit' href='#''>修改语音包</a>)"); extension.gotTextMsg(data['data'].id, data['data'].msg); sendMessageToTop(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:") { 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 = "连接失败 (<a id='voiceConnErrBtn' style='color:inherit' href='#''>帮助</a>)"; } } 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 } let GotTxtMsgCallback = undefined; class VideoTogetherFlyPannel { constructor() { this.sessionKey = "VideoTogetherFlySaveSessionKey"; this.isInRoom = false; this.isMain = (window.self == window.top); setInterval(() => { if (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 = `<style> .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,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); } .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); } </style> <div class="container" id="container"> <button id="expand-button">&lt;</button> <div style="padding: 0 5px 0 5px;" class="user-info" id="user-info"> <span class="emoji">👥</span> <span id="memberCount">0</span> </div> <button id="close-btn">x</button> <input style="margin: 0 0 0 5px;" type="text" placeholder="文字聊天" id="text-input" class="expand" /> <button id="send-button">发送</button> </div>`; 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 = '<'; sendBtn.style.display = 'inline-block'; msgInput.classList.add("expand"); } expanded = !expanded; } closeBtn.onclick = () => { shadowWrapper.style.display = "none"; } wrapper.getElementById('expand-button').addEventListener('click', () => expand()); sendBtn.onclick = () => { extension.currentSendingMsgId = generateUUID(); sendMessageToTop(MessageType.SendTxtMsg, { currentSendingMsgId: extension.currentSendingMsgId, value: msgInput.value }); } GotTxtMsgCallback = (id, msg) => { console.log(id, msg); if (id == extension.currentSendingMsgId && msg == msgInput.value) { msgInput.value = ""; } } 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"; 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 = `<div id="peer" style="display: none;"></div> <div id="videoTogetherFlyPannel" style="display: none;"> <div id="videoTogetherHeader" class="vt-modal-header"> <div style="display: flex;align-items: center;"> <img style="width: 16px; height: 16px;" src="data:image/png;base64,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"> <div class="vt-modal-title">VideoTogether</div> </div> <button style="display: none;" id="easyShareCopyBtn" type="button" class="vt-modal-title-button vt-modal-easyshare"> <span class="vt-modal-close-x"> <span role="img" aria-label="Setting" class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 32 32"> <path fill="currentColor" d="M0 25.472q0 2.368 1.664 4.032t4.032 1.664h18.944q2.336 0 4-1.664t1.664-4.032v-8.192l-3.776 3.168v5.024q0 0.8-0.544 1.344t-1.344 0.576h-18.944q-0.8 0-1.344-0.576t-0.544-1.344v-18.944q0-0.768 0.544-1.344t1.344-0.544h9.472v-3.776h-9.472q-2.368 0-4.032 1.664t-1.664 4v18.944zM5.696 19.808q0 2.752 1.088 5.28 0.512-2.944 2.24-5.344t4.288-3.872 5.632-1.664v5.6l11.36-9.472-11.36-9.472v5.664q-2.688 0-5.152 1.056t-4.224 2.848-2.848 4.224-1.024 5.152zM32 22.080v0 0 0z"> </path> </svg> </span> </span> </button> <a href="https://afdian.net/a/videotogether" target="_blank" id="vtDonate" type="button" class="vt-modal-donate vt-modal-title-button"> <span class="vt-modal-close-x"> <span role="img" class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"> <path fill="currentColor" d="M12 4.435c-1.989-5.399-12-4.597-12 3.568 0 4.068 3.06 9.481 12 14.997 8.94-5.516 12-10.929 12-14.997 0-8.118-10-8.999-12-3.568z" /> </svg> </span> </span> </a> <a href="https://setting.2gether.video/" target="_blank" id="videoTogetherSetting" type="button" aria-label="Setting" class="vt-modal-setting vt-modal-title-button"> <span class="vt-modal-close-x"> <span role="img" aria-label="Setting" class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"> <path fill="currentColor" d="M24 13.616v-3.232c-1.651-.587-2.694-.752-3.219-2.019v-.001c-.527-1.271.1-2.134.847-3.707l-2.285-2.285c-1.561.742-2.433 1.375-3.707.847h-.001c-1.269-.526-1.435-1.576-2.019-3.219h-3.232c-.582 1.635-.749 2.692-2.019 3.219h-.001c-1.271.528-2.132-.098-3.707-.847l-2.285 2.285c.745 1.568 1.375 2.434.847 3.707-.527 1.271-1.584 1.438-3.219 2.02v3.232c1.632.58 2.692.749 3.219 2.019.53 1.282-.114 2.166-.847 3.707l2.285 2.286c1.562-.743 2.434-1.375 3.707-.847h.001c1.27.526 1.436 1.579 2.019 3.219h3.232c.582-1.636.75-2.69 2.027-3.222h.001c1.262-.524 2.12.101 3.698.851l2.285-2.286c-.744-1.563-1.375-2.433-.848-3.706.527-1.271 1.588-1.44 3.221-2.021zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z" /> </svg> </span> </span> </a> <button id="videoTogetherMinimize" type="button" aria-label="Close" class="vt-modal-close vt-modal-title-button"> <span class="vt-modal-close-x"> <span role="img" aria-label="close" class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--ic" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"> <path fill="currentColor" d="M18 12.998H6a1 1 0 0 1 0-2h12a1 1 0 0 1 0 2z"></path> </svg> </span> </span> </button> </div> <div class="vt-modal-content"> <div class="vt-modal-body"> <div id="mainPannel" class="content"> <div style="height: 22.5px;"> <span id="videoTogetherRoleText"></span> <span id="memberCount"></span> </div> <div id="videoTogetherStatusText" style="height: 22.5px;"><a target='_blank' href='https://www.bilibili.com/video/BV1hM4y1n7aV/'>最新功能介绍</a></div> <div style="margin-bottom: 10px;"> <span id="videoTogetherRoomNameLabel">房间</span> <input id="videoTogetherRoomNameInput" autocomplete="off" placeholder="请输入房间名"> </div> <div> <span id="videoTogetherRoomPasswordLabel">密码</span> <input id="videoTogetherRoomPasswordInput" autocomplete="off" placeholder="输入建房密码"> </div> <div> <div id="textMessageChat" style="display: none;"> <input id="textMessageInput" autocomplete="off" placeholder="文字聊天"> <button id="textMessageSend" class="vt-btn vt-btn-primary" type="button"> <span>发送</span> </button> </div> <div id="textMessageConnecting" style="display: none;"> <span id="textMessageConnectingStatus">连接文字聊天服务器中...</span> <span id="zhcnTtsMissing">缺少中文语音包</span> </div> </div> </div> <div id="voicePannel" class="content" style="display: none;"> <div id="videoVolumeCtrl" style="margin-top: 5px;width: 100%;text-align: left;"> <span style="margin-top: 5px;display: inline-block;width: 100px;margin-left: 20px;">视频音量</span> <div class="range-slider"> <input id="videoVolume" class="slider" type="range" value="100" min="0" max="100"> </div> </div> <div id="callVolumeCtrl" style="margin-top: 5px;width: 100%;text-align: left;"> <span style="margin-top: 5px;display: inline-block;width: 100px;margin-left: 20px;">通话音量</span> <div class="range-slider"> <input id="callVolume" class="slider" type="range" value="100" min="0" max="100"> </div> </div> <div id="iosVolumeErr" style="display: none;"> <p>IOS不支持音量调节</p> </div> <!-- <div style="margin-top: 5px;width: 100%;text-align: left;"> <span style="margin-top: 0px;display: inline-block;margin-left: 20px; margin-right: 10px;">通话降噪</span> <label class="toggler-wrapper style-1"> <input id="voiceNc" type="checkbox"> <div class="toggler-slider"> <div class="toggler-knob"></div> </div> </label> </div> --> </div> </div> <div id="snackbar"></div> <div class="vt-modal-footer"> <div id="lobbyBtnGroup"> <button id="videoTogetherCreateButton" class="vt-btn vt-btn-primary" type="button"> <span>建 房</span> </button> <button id="videoTogetherJoinButton" class="vt-btn vt-btn-secondary" type="button"> <span>加 入</span> </button> </div> <div id="roomButtonGroup" style="display: none;"> <button id="videoTogetherExitButton" class="vt-btn vt-btn-dangerous" type="button"> <span>退 出</span> </button> <button id="callBtn" class="vt-btn vt-btn-dangerous" type="button"> <span>通 话</span> </button> <div id="callConnecting" class="lds-ellipsis" style="display: none;"> <div></div> <div></div> <div></div> <div></div> </div> <button id="callErrorBtn" class="vt-modal-title-button error-button" style="display: none;"> <svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor" d="M11.001 10h2v5h-2zM11 16h2v2h-2z" /> <path fill="currentColor" d="M13.768 4.2C13.42 3.545 12.742 3.138 12 3.138s-1.42.407-1.768 1.063L2.894 18.064a1.986 1.986 0 0 0 .054 1.968A1.984 1.984 0 0 0 4.661 21h14.678c.708 0 1.349-.362 1.714-.968a1.989 1.989 0 0 0 .054-1.968L13.768 4.2zM4.661 19 12 5.137 19.344 19H4.661z" /> </svg> </button> <button id="audioBtn" style="display: none;" type="button" aria-label="Close" class="vt-modal-audio vt-modal-title-button"> <span class="vt-modal-close-x"> <span class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg width="24px" height="24px" viewBox="0 0 489.6 489.6" fill="none" xmlns="http://www.w3.org/2000/svg"> <path stroke="currentColor" stroke-width="16" fill="currentColor" d="M361.1,337.6c2.2,1.5,4.6,2.3,7.1,2.3c3.8,0,7.6-1.8,10-5.2c18.7-26.3,28.5-57.4,28.5-89.9s-9.9-63.6-28.5-89.9 c-3.9-5.5-11.6-6.8-17.1-2.9c-5.5,3.9-6.8,11.6-2.9,17.1c15.7,22.1,24,48.3,24,75.8c0,27.4-8.3,53.6-24,75.8 C354.3,326.1,355.6,333.7,361.1,337.6z" /> <path stroke="currentColor" stroke-width="16" fill="currentColor" d="M425.4,396.3c2.2,1.5,4.6,2.3,7.1,2.3c3.8,0,7.6-1.8,10-5.2c30.8-43.4,47.1-94.8,47.1-148.6s-16.3-105.1-47.1-148.6 c-3.9-5.5-11.6-6.8-17.1-2.9c-5.5,3.9-6.8,11.6-2.9,17.1c27.9,39.3,42.6,85.7,42.6,134.4c0,48.6-14.7,95.1-42.6,134.4 C418.6,384.7,419.9,392.3,425.4,396.3z" /> <path stroke="currentColor" stroke-width="16" fill="currentColor" d="M254.7,415.7c4.3,2.5,9.2,3.8,14.2,3.8l0,0c7.4,0,14.4-2.8,19.7-7.9c5.6-5.4,8.7-12.6,8.7-20.4V98.5 c0-15.7-12.7-28.4-28.4-28.4c-4.9,0-9.8,1.3-14.2,3.8c-0.3,0.2-0.6,0.3-0.8,0.5l-100.1,69.2H73.3C32.9,143.6,0,176.5,0,216.9v55.6 c0,40.4,32.9,73.3,73.3,73.3h84.5l95.9,69.2C254,415.3,254.4,415.5,254.7,415.7z M161.8,321.3H73.3c-26.9,0-48.8-21.9-48.8-48.8 v-55.6c0-26.9,21.9-48.8,48.8-48.8h84.3c2.5,0,4.9-0.8,7-2.2l102.7-71c0.5-0.3,1.1-0.4,1.6-0.4c1.6,0,3.9,1.2,3.9,3.9v292.7 c0,1.1-0.4,2-1.1,2.8c-0.7,0.7-1.8,1.1-2.7,1.1c-0.5,0-1-0.1-1.5-0.3l-98.4-71.1C166.9,322.1,164.4,321.3,161.8,321.3z" /> </svg> </span> </span> </button> </div> <button id="micBtn" style="display: none;" type="button" aria-label="Close" class="vt-modal-mic vt-modal-title-button"> <span class="vt-modal-close-x"> <span class="vt-anticon vt-anticon-close vt-modal-close-icon"> <svg width="24px" height="24px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="48" height="48" fill="white" fill-opacity="0" /> <path d="M31 24V11C31 7.13401 27.866 4 24 4C20.134 4 17 7.13401 17 11V24C17 27.866 20.134 31 24 31C27.866 31 31 27.866 31 24Z" stroke="currentColor" stroke-width="4" stroke-linejoin="round" /> <path d="M9 23C9 31.2843 15.7157 38 24 38C32.2843 38 39 31.2843 39 23" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> <path d="M24 38V44" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> <path id="disabledMic" d="M42 42L6 6" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> </svg> <svg id="enabledMic" style="display: none;" width="24px" height="24px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="48" height="48" fill="white" fill-opacity="0" /> <path d="M31 24V11C31 7.13401 27.866 4 24 4C20.134 4 17 7.13401 17 11V24C17 27.866 20.134 31 24 31C27.866 31 31 27.866 31 24Z" stroke="currentColor" stroke-width="4" stroke-linejoin="round" /> <path d="M9 23C9 31.2843 15.7157 38 24 38C32.2843 38 39 31.2843 39 23" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> <path d="M24 38V44" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> </span> </button> <button id="videoTogetherHelpButton" class="vt-btn" type="button"> <span>帮 助</span> </button> </div> </div> </div> <div style="width: 24px; height: 24px;" id="videoTogetherSamllIcon"> <img draggable="false" width="24px" height="24px" id="videoTogetherMaximize" src="data:image/png;base64,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"> </img> </div> <style> #videoTogetherFlyPannel { background-color: #ffffff !important; display: block; z-index: 2147483647; position: fixed; bottom: 15px; right: 15px; 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; color: #1aa489 !important; } .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, #videoTogetherRoomPasswordInput { 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; } } </style>`; (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.easyShareCopyBtn.onclick = async () => { try { await navigator.clipboard.writeText("点击链接,和我一起看吧:<main_share_link> , 如果打不开可以尝试备用链接:<china_share_link>" .replace("<main_share_link>", extension.generateEasyShareLink()) .replace("<china_share_link>", 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 { } } 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); show(this.textMessageConnectingStatus); } if (type == 3) { show(this.textMessageConnecting); show(this.zhcnTtsMissing); } } 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); sendMessageToTop(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; } 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); 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, SendTxtMsg: 30, GotTxtMsg: 31, 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_main_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 = '1688269767'; 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 = {}; this.currentM3u8Url = undefined; this.ctxMemberCount = 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); }); // 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) } } } async gotTextMsg(id, msg, prepare = false, idx = -1) { if (idx > speechSynthesis.getVoices().length) { return; } if (!prepare && !extension.speechSynthesisEnabled) { windowPannel.ShowTxtMsgTouchPannel(); for (let i = 0; i <= 1000 && !extension.speechSynthesisEnabled; i++) { await new Promise(r => 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 < 100) { this.gotTextMsg(id, msg, prepare, idx + 1); } }); } speechSynthesis.speak(ssu); } 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(); } setTimeout(() => { 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 { }; await func(videos[i]); } catch (e) { console.error(e) }; } }); } sendMessageToSonWithContext(type, data) { if (this.isMain) { this.ctxRole = this.role; } 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, ctxRole: this.ctxRole, ctxMemberCount: this.ctxMemberCount, ctxWsIsOpen: this.ctxWsIsOpen } }); // 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, rej) => { 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 video => { 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 (isNaN(d) || Math.abs(data.duration - m3u8.duration) <= d) { d = Math.abs(data.duration - m3u8.duration); selected = m3u8; } return; }) } if (d < 3) { m3u8Url = selected.m3u8Url; } } catch { } if (data.m3u8Url == undefined) { data.m3u8Url = 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 video => { 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); 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: { window.VideoTogetherStorage = data; if (!this.isMain) { return; } 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 && !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.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: { data['m3u8Files'].forEach(m3u8 => { try { const cyrb53 = (str, seed = 0) => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < str.length; i++) { ch = str.charCodeAt(i); h1 = Math.imul(h1 ^ ch, 2654435761); h2 = Math.imul(h2 ^ ch, 1597334677); } h1 = Math.imul(h1 ^ (h1 >>> 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)}`; } } 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; } 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; } 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; } 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(' ').forEach(e => 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"); [...videos].forEach(v => _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 }) this.video_tag_names.forEach(vTag => { 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); 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 { RecoveryStateFrom.bind(this)(key => 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) { if (this.isIos == null) { this.isIos = await isAudioVolumeRO(); } WS.connect(); this.ctxWsIsOpen = WS.isOpen(); if (this.ctxWsIsOpen) { windowPannel.setTxtMsgInterface(1); } else { windowPannel.setTxtMsgInterface(2); } 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_main_host); setTimeout(() => { if (this.minTrip == 1e9 || !this.httpSucc) { this.SyncTimeWithServer(this.video_together_backup_host); } }, 3000); } else { 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(""); 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()) { 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; this.videoMap.forEach(video => { 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 { } })() "></script>
分享到:
友情链接:中国文艺网 中华人民共和国文化和旅游部 中国民间艺术网 中国民间文艺家协会 惠州文艺网 广东文艺网
版权所有: 惠州市民间文艺家协会   邮箱:hzmx2021@163.com
电话:李老师:13692898458 庄老师:13802872242  地址:惠州市下埔大道20号808室
TCP/IP备案号:粤ICP备2024213192号 |  工商备案号:粤网商备24543532号  |  广东省通信管理局 https://gdca.miit.gov.cn/    技术支持:sunkinglsx
你是第0访客