學(xué)生黨建
長(zhǎng)理交通學(xué)院組織畢業(yè)生黨員開(kāi)展考研就業(yè)分享會(huì)

“學(xué)長(zhǎng),請(qǐng)問(wèn)當(dāng)時(shí)你是如何確定自己是考研還是就業(yè)呢?”“我認(rèn)為,考研還是就業(yè)都是人生的一種選擇路徑,沒(méi)有孰優(yōu)孰劣之分,在做出決策時(shí),應(yīng)充分考慮自己的實(shí)際情況和未來(lái)發(fā)展規(guī)劃……”5月26日,長(zhǎng)沙理工大學(xué)交通運(yùn)輸工程學(xué)院各本科生黨支部“我為群眾辦實(shí)事”主題黨日活動(dòng)正在火熱進(jìn)行,畢業(yè)生黨員們就自身考研就業(yè)經(jīng)歷與學(xué)弟學(xué)妹們進(jìn)行了分享交流。

已推免至同濟(jì)大學(xué)讀研的李瑞敏從學(xué)習(xí)能力養(yǎng)成、創(chuàng)新能力培養(yǎng)、學(xué)科競(jìng)賽參與三個(gè)方面為立志推免保研的同學(xué)提供了方向和思路,她強(qiáng)調(diào)要腳踏實(shí)地,學(xué)會(huì)與焦慮共處,走好大學(xué)學(xué)習(xí)生活每一步;已成功考研至西南交通大學(xué)的李思晴以自身經(jīng)歷為例,圍繞考研動(dòng)機(jī)、擇校經(jīng)驗(yàn)、學(xué)習(xí)方法與計(jì)劃、復(fù)試技巧等幾方面給同學(xué)們提出了考研的寶貴意見(jiàn),她強(qiáng)調(diào),備戰(zhàn)考研要目標(biāo)明確,同時(shí)保持好自身復(fù)習(xí)節(jié)奏,要注重勞逸結(jié)合,保持積極心態(tài),方可事半功倍……

已簽約浙江交工集團(tuán)的鄭淇真結(jié)合自己在多個(gè)公司、不同崗位的企業(yè)實(shí)習(xí)經(jīng)歷,從總體就業(yè)形勢(shì)、求職準(zhǔn)備、簡(jiǎn)歷制作和面試技巧四個(gè)方面深入分享了在實(shí)習(xí)和求職過(guò)程中的心得體會(huì),鼓勵(lì)大家要勇于嘗試、敢于探索,勇敢跳出“舒適圈”,將自我價(jià)值實(shí)現(xiàn)與祖國(guó)需要相結(jié)合,到祖國(guó)最需要的地方去,為交通強(qiáng)國(guó)建設(shè)建功立業(yè);此外,幾名考公同志結(jié)合自身備考經(jīng)驗(yàn),從考情分析、選崗建議、復(fù)習(xí)計(jì)劃等多角度就各類公務(wù)員考試進(jìn)行了分享等。

會(huì)后,各支部均設(shè)立了交流環(huán)節(jié),對(duì)同學(xué)們的疑問(wèn)進(jìn)行逐一解答。大二學(xué)生張思思表示:“通過(guò)此次分享會(huì),我明白了人生的選擇有很多,我會(huì)努力破除固有的觀念,進(jìn)一步審視自我、剖析自我,明確自身發(fā)展需求、明確社會(huì)發(fā)展大勢(shì)、明確黨和國(guó)家需要,在了解中明晰個(gè)人職業(yè)規(guī)劃,堅(jiān)定信心、勇往直前!”

在畢業(yè)季來(lái)臨之際,交通學(xué)院持續(xù)加強(qiáng)畢業(yè)生黨員教育管理,通過(guò)開(kāi)展理論學(xué)習(xí)、黨紀(jì)教育、志愿服務(wù)、主題座談、經(jīng)驗(yàn)分享等各類畢業(yè)教育活動(dòng),引導(dǎo)畢業(yè)生黨員站好畢業(yè)離校前“最后一班崗”,自覺(jué)增強(qiáng)政治意識(shí)、愛(ài)校情結(jié)和感恩意識(shí),服務(wù)好學(xué)生黨員在?!白詈笠还铩?/span>。

    (文/圖 伍思豪 一審/肖敏強(qiáng) 二審/汪招霞 三審/任志祥)



?? 0
`; document.fullscreenElement.appendChild(shadowWrapper); var container = wrapper.getElementById('container'); let expandBtn = wrapper.getElementById('expand-button'); let msgInput = wrapper.getElementById('text-input'); let sendBtn = wrapper.getElementById('send-button'); let closeBtn = wrapper.getElementById('close-btn'); let expanded = true; function expand() { if (expanded) { expandBtn.innerText = '>' sendBtn.style.display = 'none'; msgInput.classList.remove('expand'); } else { expandBtn.innerText = '<'; 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"; shadowWrapper.ontouchstart = (e) => { e.stopPropagation() } let wrapper; try { wrapper = AttachShadow(shadowWrapper, { mode: "open" }); wrapper.addEventListener('keydown', (e) => e.stopPropagation()) } catch (e) { console.error(e); } this.shadowWrapper = shadowWrapper; this.wrapper = wrapper; wrapper.innerHTML = `
VideoTogether
房間
密碼
連接文字聊天服務(wù)器中... 缺少中文語(yǔ)音包
檢測(cè)視頻中...
下載中,不要關(guān)閉頁(yè)面 下載完成
視頻音量
通話音量

IOS不支持音量調(diào)節(jié)

`; (document.body || document.documentElement).appendChild(shadowWrapper); wrapper.querySelector("#videoTogetherMinimize").onclick = () => { this.Minimize() } wrapper.querySelector("#videoTogetherMaximize").onclick = () => { this.Maximize() } ["", "webkit"].forEach(prefix => { document.addEventListener(prefix + "fullscreenchange", (event) => { if (document.fullscreenElement || document.webkitFullscreenElement) { hide(this.videoTogetherFlyPannel); hide(this.videoTogetherSamllIcon); } else { if (this.minimized) { this.Minimize(); } else { this.Maximize(); } } }); }); wrapper.querySelector("#textMessageInput").addEventListener("keyup", e => { if (e.key == "Enter") { wrapper.querySelector("#textMessageSend").click(); } }); wrapper.querySelector("#textMessageSend").onclick = async () => { extension.currentSendingMsgId = generateUUID(); WS.sendTextMessage(extension.currentSendingMsgId, select("#textMessageInput").value); } this.lobbyBtnGroup = wrapper.querySelector("#lobbyBtnGroup"); this.createRoomButton = wrapper.querySelector('#videoTogetherCreateButton'); this.joinRoomButton = wrapper.querySelector("#videoTogetherJoinButton"); this.roomButtonGroup = wrapper.querySelector('#roomButtonGroup'); this.exitButton = wrapper.querySelector("#videoTogetherExitButton"); this.callBtn = wrapper.querySelector("#callBtn"); this.callBtn.onclick = () => Voice.join("", window.videoTogetherExtension.roomName); this.helpButton = wrapper.querySelector("#videoTogetherHelpButton"); this.audioBtn = wrapper.querySelector("#audioBtn"); this.micBtn = wrapper.querySelector("#micBtn"); this.videoVolume = wrapper.querySelector("#videoVolume"); this.callVolumeSlider = wrapper.querySelector("#callVolume"); this.callErrorBtn = wrapper.querySelector("#callErrorBtn"); this.easyShareCopyBtn = wrapper.querySelector("#easyShareCopyBtn"); this.textMessageChat = wrapper.querySelector("#textMessageChat"); this.textMessageConnecting = wrapper.querySelector("#textMessageConnecting"); this.textMessageConnectingStatus = wrapper.querySelector("#textMessageConnectingStatus"); this.zhcnTtsMissing = wrapper.querySelector("#zhcnTtsMissing"); this.downloadBtn = wrapper.querySelector("#downloadBtn"); hide(this.downloadBtn); this.confirmDownloadBtn = wrapper.querySelector("#confirmDownloadBtn") this.confirmDownloadBtn.onclick = () => { if (extension.downloadM3u8UrlType == "video") { extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_video_download") console.log(extension.downloadM3u8Url, extension.downloadM3u8UrlType) sendMessageToTop(MessageType.SetStorageValue, { key: "PublicNextDownload", value: { filename: document.title + '.mp4', url: extension.downloadM3u8Url } }); const a = document.createElement("a"); a.href = extension.downloadM3u8Url; a.target = "_blank"; a.download = document.title + ".mp4"; a.click(); return; } extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_m3u8_download") isDownloading = true; const m3u8url = extension.downloadM3u8Url sendMessageTo(extension.m3u8PostWindows[extension.GetM3u8WindowId(m3u8url)], MessageType.StartDownload, { m3u8Url: m3u8url, m3u8Content: extension.GetM3u8Content(m3u8url), urls: extension.GetAllM3u8SegUrls(m3u8url), title: document.title, pageUrl: window.location.href }); hide(this.confirmDownloadBtn); show(select("#downloadProgress")); } this.downloadBtn.onclick = () => { setInterval(() => { if (isDownloading) { return; } if (extension.downloadM3u8Url != undefined) { show(this.confirmDownloadBtn); select('#downloadVideoInfo').innerText = getDurationStr(extension.downloadDuration); } else { hide(this.confirmDownloadBtn); select('#downloadVideoInfo').innerText = "檢測(cè)視頻中..." } }, 1000); inDownload = true; this.inputRoomName.value = "download_" + generateUUID(); this.createRoomButton.click() hide(select('.vt-modal-footer')) hide(select('#mainPannel')) show(select('#downloadPannel')) } this.easyShareCopyBtn.onclick = async () => { try { if (isWeb()) { await navigator.clipboard.writeText(extension.linkWithMemberState(window.location, extension.RoleEnum.Member, false)) } else { await navigator.clipboard.writeText("點(diǎn)擊鏈接,和我一起看吧:, 如果打不開(kāi)可以嘗試備用鏈接:" .replace("", extension.generateEasyShareLink()) .replace("", extension.generateEasyShareLink(true))); } popupError("復(fù)制成功,快去分享吧"); } catch { popupError("復(fù)制失敗"); } } 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("#videoTogetherRoomPdIpt"); this.inputRoomNameLabel = wrapper.querySelector('#videoTogetherRoomNameLabel'); this.inputRoomPasswordLabel = wrapper.querySelector("#videoTogetherRoomPasswordLabel"); this.videoTogetherHeader = wrapper.querySelector("#videoTogetherHeader"); this.videoTogetherFlyPannel = wrapper.getElementById("videoTogetherFlyPannel"); this.videoTogetherSamllIcon = wrapper.getElementById("videoTogetherSamllIcon"); this.volume = 1; this.statusText = wrapper.querySelector("#videoTogetherStatusText"); this.InLobby(true); this.Init(); setInterval(() => { this.ShowPannel(); }, 1000); } try { document.querySelector("#videoTogetherLoading").remove() } catch { } } ShowTxtMsgTouchPannel() { try { function exitFullScreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { /* Safari */ document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { /* Firefox */ document.mozCancelFullScreen(); } } exitFullScreen(); } catch { } try { this.txtMsgTouchPannel.remove(); } catch { } this.txtMsgTouchPannel = document.createElement('div'); let touch = this.txtMsgTouchPannel; touch.id = "videoTogetherTxtMsgTouch"; touch.style.width = "100%"; touch.style.height = "100%"; touch.style.position = "fixed"; touch.style.top = "0"; touch.style.left = "0"; touch.style.zIndex = "2147483647"; touch.style.background = "#fff"; touch.style.display = "flex"; touch.style.justifyContent = "center"; touch.style.alignItems = "center"; touch.style.padding = "0px"; touch.style.flexDirection = "column"; touch.style.lineHeight = "40px"; AttachShadow(this.txtMsgTouchPannel, { mode: "open" }) touch.addEventListener('click', function () { windowPannel.enableSpeechSynthesis(); document.body.removeChild(touch); windowPannel.txtMsgTouchPannel = undefined; }); document.body.appendChild(touch); this.setTxtMsgTouchPannelText("VideoTogether: 您有一條新消息,點(diǎn)擊屏幕接收"); } setTxtMsgInterface(type) { hide(this.textMessageChat); hide(this.textMessageConnecting); hide(this.textMessageConnectingStatus); hide(this.zhcnTtsMissing); if (type == 0) { } if (type == 1) { show(this.textMessageChat); } if (type == 2) { show(this.textMessageConnecting); this.textMessageConnectingStatus.innerText = "連接文字聊天服務(wù)器中..." show(this.textMessageConnectingStatus); } if (type == 3) { show(this.textMessageConnecting); show(this.zhcnTtsMissing); } if (type == 4) { show(this.textMessageConnecting); this.textMessageConnectingStatus.innerText = "文字聊天已關(guān)閉" show(this.textMessageConnectingStatus); } } enableSpeechSynthesis() { if (!extension.speechSynthesisEnabled) { try { extension.gotTextMsg("", "", true); extension.speechSynthesisEnabled = true; } catch { } } } setTxtMsgTouchPannelText(s) { let span = document.createElement('span'); span.style.fontSize = "40px"; span.style.lineHeight = "40px"; span.style.color = "black"; span.style.overflowWrap = "break-word"; span.style.textAlign = "center"; span.textContent = s; this.txtMsgTouchPannel.shadowRoot.appendChild(span); let voiceSelect = document.createElement('select'); this.voiceSelect = voiceSelect; voiceSelect.onclick = (e) => { e.stopPropagation(); } let label = span.cloneNode(true); label.textContent = "你可以在下方選擇朗讀信息的語(yǔ)音:"; 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; hide(this.downloadBtn) } InLobby(init = false) { if (!init) { this.Maximize(); } this.inputRoomName.disabled = false; this.inputRoomPasswordLabel.style.display = "inline-block"; this.inputRoomPassword.style.display = "inline-block"; this.inputRoomName.placeholder = "請(qǐng)輸入房間名" show(this.lobbyBtnGroup); hide(this.roomButtonGroup); hide(this.easyShareCopyBtn); this.setTxtMsgInterface(0); dsply(this.downloadBtn, downloadEnabled()) this.isInRoom = false; } CreateRoomButtonOnClick() { this.Maximize(); let roomName = this.inputRoomName.value; let password = this.inputRoomPassword.value; window.videoTogetherExtension.CreateRoom(roomName, password); } JoinRoomButtonOnClick() { this.Maximize(); let roomName = this.inputRoomName.value; let password = this.inputRoomPassword.value; window.videoTogetherExtension.JoinRoom(roomName, password); } HelpButtonOnClick() { this.Maximize(); let url = 'https://2gether.video/guide/qa.html'; if (vtRuntime == "website") { url = "https://2gether.video/guide/website_qa.html" } window.open(url, '_blank'); } UpdateStatusText(text, color) { this.statusText.innerHTML = text; this.statusText.style.color = color; } } class VideoModel { constructor(id, duration, activatedTime, refreshTime, priority = 0) { this.id = id; this.duration = duration; this.activatedTime = activatedTime; this.refreshTime = refreshTime; this.priority = priority; } } let MessageType = { ActivatedVideo: 1, ReportVideo: 2, SyncMemberVideo: 3, SyncMasterVideo: 4, UpdateStatusText: 5, JumpToNewPage: 6, GetRoomData: 7, ChangeVoiceVolume: 8, ChangeVideoVolume: 9, FetchRequest: 13, FetchResponse: 14, SetStorageValue: 15, SyncStorageValue: 16, ExtensionInitSuccess: 17, SetTabStorage: 18, SetTabStorageSuccess: 19, UpdateRoomRequest: 20, CallScheduledTask: 21, RoomDataNotification: 22, UpdateMemberStatus: 23, TimestampV2Resp: 24, // EasyShareCheckSucc: 25, FetchRealUrlReq: 26, FetchRealUrlResp: 27, FetchRealUrlFromIframeReq: 28, FetchRealUrlFromIframeResp: 29, SendTxtMsg: 30, GotTxtMsg: 31, StartDownload: 32, DownloadStatus: 33, UpdateM3u8Files: 1001, SaveIndexedDb: 2001, ReadIndexedDb: 2002, SaveIndexedDbResult: 2003, ReadIndexedDbResult: 2004, RegexMatchKeysDb: 2005, RegexMatchKeysDbResult: 2006, DeleteFromIndexedDb: 2007, DeleteFromIndexedDbResult: 2008, StorageEstimate: 2009, StorageEstimateResult: 2010, ReadIndexedDbSw: 2011, ReadIndexedDbSwResult: 2012, //2013 used IosStorageSet: 3001, IosStorageSetResult: 3002, IosStorageGet: 3003, IosStorageGetResult: 3004, IosStorageDelete: 3005, IosStorageDeleteResult: 3006, IosStorageUsage: 3007, IosStorageUsageResult: 3008, IosStorageCompact: 3009, IosStorageDeletePrefix: 3010, IosStorageDeletePrefixResult: 3011, } let VIDEO_EXPIRED_SECOND = 10 class VideoWrapper { set currentTime(v) { this.currentTimeSetter(v); } get currentTime() { return this.currentTimeGetter(); } set playbackRate(v) { this.playbackRateSetter(v); } get playbackRate() { return this.playbackRateGetter(); } constructor(play, pause, paused, currentTimeGetter, currentTimeSetter, duration, playbackRateGetter, playbackRateSetter) { this.play = play; this.pause = pause; this.paused = paused; this.currentTimeGetter = currentTimeGetter; this.currentTimeSetter = currentTimeSetter; this.duration = duration; this.playbackRateGetter = playbackRateGetter; this.playbackRateSetter = playbackRateSetter; } } class VideoTogetherExtension { constructor() { this.RoleEnum = { Null: 1, Master: 2, Member: 3, } this.cspBlockedHost = {}; this.video_together_host = 'https://vt.panghair.com:5000/'; this.video_together_main_host = 'https://vt.panghair.com:5000/'; this.video_together_backup_host = 'https://api.chizhou.in/'; this.video_tag_names = ["video", "bwp-video", "fake-iframe-video"] this.timer = 0 this.roomName = "" this.roomPassword = "" this.role = this.RoleEnum.Null this.url = "" this.duration = undefined this.waitForLoadding = false; this.playAfterLoadding = false; this.minTrip = 1e9; this.timeOffset = 0; this.lastScheduledTaskTs = 0; this.httpSucc = false; this.activatedVideo = undefined; this.tempUser = generateTempUserId(); this.version = '1707141762'; this.isMain = (window.self == window.top); this.UserId = undefined; this.callbackMap = new Map; this.allLinksTargetModified = false; this.voiceVolume = null; this.videoVolume = null; this.m3u8Files = {}; this.m3u8DurationReCal = {}; this.m3u8UrlTestResult = {}; this.hasCheckedM3u8Url = {}; this.m3u8PostWindows = {}; this.m3u8MediaUrls = {}; this.currentM3u8Url = undefined; this.ctxMemberCount = 0; this.downloadSpeedMb = 0; this.downloadPercentage = 0; this.currentSendingMsgId = null; this.isIos = undefined; this.speechSynthesisEnabled = false; // we need a common callback function to deal with all message this.SetTabStorageSuccessCallback = () => { }; document.addEventListener("securitypolicyviolation", (e) => { let host = (new URL(e.blockedURI)).host; this.cspBlockedHost[host] = true; }); try { this.CreateVideoDomObserver(); } catch { } this.timer = setInterval(() => this.ScheduledTask(true), 2 * 1000); this.videoMap = new Map(); window.addEventListener('message', message => { if (message.data.context) { this.tempUser = message.data.context.tempUser; this.videoTitle = message.data.context.videoTitle; this.voiceStatus = message.data.context.voiceStatus; this.timeOffset = message.data.context.timeOffset; this.ctxRole = message.data.context.ctxRole; this.ctxMemberCount = message.data.context.ctxMemberCount; this.ctxWsIsOpen = message.data.context.ctxWsIsOpen; // sub frame has 2 storage data source, top frame or extension.js in this frame // this 2 data source should be same. window.VideoTogetherStorage = message.data.context.VideoTogetherStorage; } this.processReceivedMessage(message.data.type, message.data.data, message); }); try { navigator.serviceWorker.addEventListener('message', (message) => { console.log(`Received a message from service worker: ${event.data}`); this.processReceivedMessage(message.data.type, message.data.data, message); }); } catch { }; // if some element's click be invoked frequenctly, a lot of http request will be sent // window.addEventListener('click', message => { // setTimeout(this.ScheduledTask.bind(this), 200); // }) if (this.isMain) { try { try { this.RecoveryState(); } catch { } this.EnableDraggable(); setTimeout(() => { let allDoms = document.querySelectorAll("*"); for (let i = 0; i < 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 '' } 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: "超時(shí)" }); } } finally { this.callbackMap.delete(id); } }, 20000); }); } try { if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.fetch))) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); return await window.fetch(url, { method: method, body: data == null ? undefined : JSON.stringify(data), signal: controller.signal }); } else { GetNativeFunction(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); return await Global.NativeFetch.call(window, url, { method: method, body: data == null ? undefined : JSON.stringify(data), signal: controller.signal }); } } catch (e) { const host = new URL(extension.video_together_host); const requestUrl = new URL(url); if (host.hostname == requestUrl.hostname) { extension.httpSucc = false; } throw e; } } async ForEachVideo(func) { try { if (window.location.hostname.endsWith("iqiyi.com")) { let video = document.querySelector('.iqp-player-videolayer-inner > video'); if (video != null) { video.VideoTogetherChoosed = true; try { await func(video) } catch { }; } } // disneyplus if (window.location.hostname.endsWith("disneyplus.com")) { try { let ff = document.querySelector('.ff-10sec-icon'); let rr = document.querySelector('.rwd-10sec-icon'); let video = document.querySelector('video'); if (ff && rr && video) { if (!video.videoTogetherVideoWrapper) { video.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = video.videoTogetherVideoWrapper; videoWrapper.play = async () => await video.play(); videoWrapper.pause = async () => await video.pause(); videoWrapper.paused = video.paused videoWrapper.currentTimeGetter = () => video.currentTime; videoWrapper.currentTimeSetter = (v) => { let isFf = v > video.currentTime; let d = Math.abs(v - video.currentTime); let clickTime = parseInt(d / 10); if (clickTime > 0) { console.log(clickTime); } for (let i = 0; i < 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) { } } // 百度網(wǎng)盤 if (window.location.host.includes('pan.baidu.com')) { if (!this.BaiduPanPlayer) { try { if (document.querySelector('.vjs-controls-enabled').player != undefined) { this.BaiduPanPlayer = document.querySelector('.vjs-controls-enabled').player; } } catch { } } if (this.BaiduPanPlayer) { if (!this.BaiduPanPlayer.videoTogetherVideoWrapper) { this.BaiduPanPlayer.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = this.BaiduPanPlayer.videoTogetherVideoWrapper; videoWrapper.play = async () => await this.BaiduPanPlayer.play(); videoWrapper.pause = async () => await this.BaiduPanPlayer.pause(); videoWrapper.paused = this.BaiduPanPlayer.paused(); videoWrapper.currentTimeGetter = () => this.BaiduPanPlayer.currentTime(); videoWrapper.currentTimeSetter = (v) => this.BaiduPanPlayer.currentTime(v); videoWrapper.duration = this.BaiduPanPlayer.duration(); videoWrapper.playbackRateGetter = () => this.BaiduPanPlayer.playbackRate(); videoWrapper.playbackRateSetter = (v) => this.BaiduPanPlayer.playbackRate(v); await func(videoWrapper); } } } catch (e) { } try { // 騰訊視頻 if (window.__PLAYER__ != undefined) { if (window.__PLAYER__.videoTogetherVideoWrapper == undefined) { window.__PLAYER__.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = window.__PLAYER__.videoTogetherVideoWrapper; videoWrapper.play = async () => await window.__PLAYER__.corePlayer.play(); videoWrapper.pause = async () => await window.__PLAYER__.corePlayer.pause(); videoWrapper.paused = window.__PLAYER__.paused; videoWrapper.currentTimeGetter = () => window.__PLAYER__.currentVideoInfo.playtime; videoWrapper.currentTimeSetter = (v) => { if (!videoWrapper.videoTogetherPaused) { window.__PLAYER__.seek(v) } }; videoWrapper.duration = window.__PLAYER__.currentVideoInfo.duration; videoWrapper.playbackRateGetter = () => window.__PLAYER__.playbackRate; videoWrapper.playbackRateSetter = (v) => window.__PLAYER__.playbackRate = v; await func(videoWrapper); } } catch (e) { }; this.video_tag_names.forEach(async tag => { let videos = document.getElementsByTagName(tag); for (let i = 0; i < videos.length; i++) { try { try { if (videos[i].VideoTogetherDisabled) { continue; } } catch { }; try { if (window.location.hostname.endsWith('bilibili.com')) { if (!!videos[i].closest('div.video-page-card-small') || !!videos[i].closest('div.feed-card')) { // this is a thumbnail video continue } } } catch { } await func(videos[i]); } catch (e) { console.error(e) }; } }); } sendMessageToSonWithContext(type, data) { if (this.isMain) { this.ctxRole = this.role; } 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; } GetM3u8WindowId(m3u8Url) { let windowId = undefined; for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { if (m3u8Url == m3u8.m3u8Url) { windowId = id; } }) } return windowId; } UrlRequest(m3u8Url, idx, origin) { for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { if (m3u8Url == m3u8.m3u8Url) { let urls = extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url); let url = urls[idx]; sendMessageTo(this.m3u8PostWindows[id], MessageType.FetchRealUrlReq, { url: url, origin: origin }); } }) } } async testM3u8OrVideoUrl(testUrl) { const onsecuritypolicyviolation = (e) => { if (e.blockedURI == testUrl) { // m3u8 can always be fetched, because hls.js this.m3u8UrlTestResult[testUrl] = 'video' } } document.addEventListener("securitypolicyviolation", onsecuritypolicyviolation) if (this.m3u8UrlTestResult[testUrl] != undefined) { return this.m3u8UrlTestResult[testUrl]; } function limitStream(stream, limit) { const reader = stream.getReader(); let bytesRead = 0; return new ReadableStream({ async pull(controller) { const { value, done } = await reader.read(); if (done || bytesRead >= limit) { controller.close(); return; } bytesRead += value.byteLength; controller.enqueue(value); }, cancel(reason) { reader.cancel(reason); } }); } return new Promise((res, rej) => { const rtnType = (tp) => { if (this.m3u8UrlTestResult[testUrl] == undefined) { this.m3u8UrlTestResult[testUrl] = tp } res(this.m3u8UrlTestResult[testUrl]) } const abortController = new AbortController(); VideoTogetherFetch(testUrl, { signal: abortController.signal }).then(response => { const contentType = response.headers.get('Content-Type') if (contentType.startsWith('video/')) { rtnType('video'); } const limitedStream = limitStream(response.body, 1024); // Limit to 1024 bytes return new Response(limitedStream, { headers: response.headers }); }).then(r => r.text()) .then(async txt => { abortController.abort(); if (isM3U8(txt)) { rtnType('m3u8'); } else { rtnType('video'); } }).catch(e => { if (testUrl.startsWith('blob')) { rtnType('unknown'); } else { rtnType('video'); } }).finally(() => { document.removeEventListener("securitypolicyviolation", onsecuritypolicyviolation) }) }) } // download GetAllM3u8SegUrls(m3u8Url) { for (let id in this.m3u8Files) { for (let mid in this.m3u8Files[id]) { let m3u8 = this.m3u8Files[id][mid] if (m3u8Url == m3u8.m3u8Url) { return extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url); } } } } // end of download UpdateStatusText(text, color) { if (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; try { let d = NaN; let selected = null; for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { // here m3u8Url may be empty, may caused by the new response // from limitedstream, but we have a new fetch after that, // so we can always get the correct url. if (isNaN(d) || Math.abs(data.duration - m3u8.duration) <= d) { d = Math.abs(data.duration - m3u8.duration); selected = m3u8; } return; }) } if (d < 3 || d / data.duration < 0.03) { m3u8Url = selected.m3u8Url; } } catch { } if (data.m3u8Url == undefined) { data.m3u8Url = m3u8Url; } else { }// data.m3u8Url may be a video file if (data.m3u8UrlType == 'video') { this.downloadM3u8Url = data.m3u8Url; this.downloadM3u8UrlType = 'video'; this.downloadDuration = data.duration; } else { if (m3u8Url != undefined) { this.downloadM3u8Url = m3u8Url; this.downloadDuration = data.duration; this.downloadM3u8UrlType = 'm3u8'; // video or other } else { this.downloadM3u8Url = undefined; this.downloadDuration = undefined; } } if (!isEasyShareEnabled()) { data.m3u8Url = ""; } try { if (!isEmpty(data.m3u8Url) && isEasyShareEnabled()) { this.currentM3u8Url = data.m3u8Url; show(windowPannel.easyShareCopyBtn); } else { this.currentM3u8Url = undefined; if (isWeb()) { show(windowPannel.easyShareCopyBtn); } else { 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); if (newUrl.hash != "") { currentUrl.hash = ""; newUrl.hash = ""; if (currentUrl.href == newUrl.href) { extension.url = data.url; // window.location.reload();// for hash change } } break; case MessageType.ChangeVideoVolume: this.ForEachVideo(video => { video.volume = data.volume; }); this.sendMessageToSonWithContext(type, data); break; case MessageType.FetchResponse: { try { this.callbackMap.get(data.id)(data); } catch { }; break; } case MessageType.SyncStorageValue: { const firstSync = (window.VideoTogetherSettingEnabled == undefined) window.VideoTogetherStorage = data; if (!this.isMain) { return; } try { if (window.VideoTogetherStorage.PublicNextDownload.url == window.location.href && this.HasDownload != true) { const a = document.createElement("a"); a.href = window.VideoTogetherStorage.PublicNextDownload.url; a.download = window.VideoTogetherStorage.PublicNextDownload.filename; a.click(); this.HasDownload = true; } } catch { } try { if (!this.RecoveryStateFromTab) { this.RecoveryStateFromTab = true; this.RecoveryState() } } catch (e) { }; try { if (data.PublicMessageVoice != null) { windowPannel.voiceSelect.value = data.PublicMessageVoice; } } catch { }; if (!window.videoTogetherFlyPannel.disableDefaultSize && firstSync) { if (data.MinimiseDefault) { window.videoTogetherFlyPannel.Minimize(true); } else { window.videoTogetherFlyPannel.Maximize(true); } } if (typeof (data.PublicUserId) != 'string' || data.PublicUserId.length < 5) { sendMessageToTop(MessageType.SetStorageValue, { key: "PublicUserId", value: generateUUID() }); } try { if (firstSync) { 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) { } try { dsply(select('#downloadBtn'), downloadEnabled() && !windowPannel.isInRoom) } catch { } 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 { function calculateM3U8Duration(textContent) { let totalDuration = 0; const lines = textContent.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith('#EXTINF:')) { if (i + 1 >= lines.length || lines[i + 1].startsWith('#')) { continue; } let durationLine = lines[i]; let durationParts = durationLine.split(':'); if (durationParts.length > 1) { let durationValue = durationParts[1].split(',')[0]; let duration = parseFloat(durationValue); if (!isNaN(duration)) { totalDuration += duration; } } } } return totalDuration; } const cyrb53 = (str, seed = 0) => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < 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)}`; } if (this.m3u8DurationReCal[m3u8.m3u8Url] == undefined) { this.m3u8DurationReCal[m3u8.m3u8Url] = calculateM3U8Duration(m3u8.m3u8Content); } m3u8.duration = this.m3u8DurationReCal[m3u8.m3u8Url]; } catch { } }) this.m3u8Files[data['id']] = data['m3u8Files']; this.m3u8PostWindows[data['id']] = _msg.source; break; } case MessageType.FetchRealUrlReq: { console.log(data); if (realUrlCache[data.url] == undefined) { const controller = new AbortController(); let r = await Fetch(data.url, { method: "GET", signal: controller.signal }); controller.abort(); realUrlCache[data.url] = r.url; } 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; } case MessageType.ReadIndexedDbSw: { const result = await readFromIndexedDB(data.table, data.key); data.data = result navigator.serviceWorker.controller.postMessage({ source: "VideoTogether", type: 2012, data: data }); break; } case MessageType.StartDownload: { startDownload(data.m3u8Url, data.m3u8Content, data.urls, data.title, data.pageUrl); setInterval(() => { sendMessageToTop(MessageType.DownloadStatus, { downloadSpeedMb: this.downloadSpeedMb, downloadPercentage: this.downloadPercentage }) }, 1000) break; } case MessageType.DownloadStatus: { extension.downloadSpeedMb = data.downloadSpeedMb; extension.downloadPercentage = data.downloadPercentage; if (extension.downloadPercentage == 100) { if (this.downloadM3u8Completed != true) { this.downloadM3u8Completed = true; extension.Fetch(extension.video_together_host + "/beta/counter?key=download_m3u8_completed") } hide(select("#downloadingAlert")) show(select("#downloadCompleted")) } select("#downloadStatus").innerText = extension.downloadPercentage + "% " select("#downloadSpeed").innerText = extension.downloadSpeedMb.toFixed(2) + "MB/s" select("#downloadProgressBar").value = extension.downloadPercentage break; } default: // console.info("unhandled message:", type, data) break; } } openAllLinksInSelf() { let hrefs = document.getElementsByTagName("a"); for (let i = 0; i < hrefs.length; i++) { hrefs[i].target = "_self"; } } async RunWithRetry(func, count) { for (let i = 0; i < 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("請(qǐng)輸入房間名") 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("請(qǐng)注意調(diào)試模式已開(kāi)啟, 您的隱私很有可能會(huì)被泄漏"); (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 (!getEnableTextMessage()) { windowPannel.setTxtMsgInterface(4); } else 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 { // TODO // if (this.video_together_host == this.video_together_backup_host) { // this.SyncTimeWithServer(this.video_together_main_host); // } } } catch { }; } try { switch (this.role) { case this.RoleEnum.Null: return; case this.RoleEnum.Master: { if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) { let state = this.GetRoomState(""); 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("頁(yè)面沒(méi)有視頻"); } 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("該視頻無(wú)法同步"); } 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("請(qǐng)?jiān)谔D(zhuǎn)后再次加入"); } } } 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("頁(yè)面沒(méi)有視頻"); } 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) { try { if (this.isMain) { useMobileStyle(videoDom); } } catch { } 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; let m3u8UrlType; try { let nativeSrc = videoDom.src; if (nativeSrc == "" || nativeSrc == undefined) { nativeSrc = videoDom.querySelector('source').src; } nativeSrc = new URL(nativeSrc, window.location).href if (nativeSrc.startsWith('http')) { m3u8Url = nativeSrc; } this.testM3u8OrVideoUrl(nativeSrc).then(r => { if (r == 'm3u8' && this.hasCheckedM3u8Url[nativeSrc] != true) { fetch(nativeSrc).then(r => r.text()).then(m3u8Content => { if (isMasterM3u8(m3u8Content)) { const mediaM3u8Url = getFirstMediaM3U8(m3u8Content, nativeSrc); fetch(mediaM3u8Url).then(r => r.text()).then(() => { this.hasCheckedM3u8Url[nativeSrc] = true; }) } else { this.hasCheckedM3u8Url[nativeSrc] = true; } } ) } }) m3u8UrlType = this.m3u8UrlTestResult[nativeSrc] } catch { }; sendMessageToTop(MessageType.UpdateRoomRequest, { name: data.roomName, password: data.password, url: data.link, playbackRate: videoDom.playbackRate, currentTime: videoDom.currentTime, paused: paused, duration: videoDom.duration, localTimestamp: this.getLocalTimestamp(), m3u8Url: m3u8Url, m3u8UrlType: m3u8UrlType }) } linkWithoutState(link) { let url = new URL(link); url.searchParams.delete("VideoTogetherUrl"); url.searchParams.delete("VideoTogetherRoomName"); url.searchParams.delete("VideoTogetherRole"); url.searchParams.delete("VideoTogetherPassword"); url.searchParams.delete("VideoTogetherTimestamp"); return url.toString(); } GetRoomState(link) { if (inDownload) { return {}; } if (this.role == this.RoleEnum.Null) { return {}; } let voice = Voice.status; if (voice == VoiceStatus.CONNECTTING) { try { voice = window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherVoice; } catch { voice = VoiceStatus.STOP; } } return { VideoTogetherUrl: link, VideoTogetherRoomName: this.roomName, VideoTogetherPassword: this.password, VideoTogetherRole: this.role, VideoTogetherTimestamp: Date.now() / 1000, VideoTogetherVoice: voice, VideoVolume: this.getVideoVolume(), VoiceVolume: this.getVoiceVolume() } } SaveStateToSessionStorageWhenSameOrigin(link) { if (inDownload) { return false; } try { let sameOrigin = false; if (link != "") { let url = new URL(link); let currentUrl = new URL(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, newRole = undefined, expire = true) { let url = new URL(link); let tmpSearch = url.search; url.search = ""; url.searchParams.set("VideoTogetherUrl", link); url.searchParams.set("VideoTogetherRoomName", this.roomName); url.searchParams.set("VideoTogetherPassword", this.password); url.searchParams.set("VideoTogetherRole", newRole ? newRole : this.role); url.searchParams.set("VideoTogetherTimestamp", expire ? Date.now() / 1000 : 1e10); let urlStr = url.toString(); if (tmpSearch.length > 1) { urlStr = urlStr + "&" + tmpSearch.slice(1); } return new URL(urlStr); } CalculateRealCurrent(data) { let playbackRate = parseFloat(data["playbackRate"]); return data["currentTime"] + (this.getLocalTimestamp() - data["lastUpdateClientTime"]) * (isNaN(playbackRate) ? 1 : playbackRate); } GetDisplayTimeText() { let date = new Date(); return date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); } async SyncMemberVideo(data, videoDom) { try { if (this.isMain) { useMobileStyle(videoDom); } } catch { } if (this.lastSyncMemberVideo + 1 > Date.now() / 1000) { return; } this.lastSyncMemberVideo = Date.now() / 1000; let room = data.room; sendMessageToTop(MessageType.GetRoomData, room); // useless this.duration = room["duration"]; // useless if (videoDom == undefined) { throw new Error("沒(méi)有視頻"); } 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("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } } await videoDom.play(); if (videoDom.paused) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } catch (e) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } } if (videoDom.playbackRate != room["playbackRate"]) { try { videoDom.playbackRate = parseFloat(room["playbackRate"]); } catch (e) { } } if (isNaN(videoDom.duration)) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } 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("請(qǐng)輸入房間名") 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; } } try { if (window.location.hostname == 'yiyan.baidu.com') { GetNativeFunction(); window.Element.prototype.attachShadow = Global.NativeAttachShadow; console.log("Use native attachShadow in yiyan") } } catch { } // 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 { }})()">
地址:湖南省長(zhǎng)沙市天心區(qū)萬(wàn)家麗南路二段960號(hào)
郵編:410114
電話:0731-85258575
傳真:0731-85258375
長(zhǎng)沙理工大學(xué)交通運(yùn)輸工程學(xué)院 版權(quán)所有
學(xué)生黨建
學(xué)生黨建
當(dāng)前位置: 網(wǎng)站首頁(yè) / 學(xué)生工作 / 學(xué)生黨建 / 正文
學(xué)生黨建
長(zhǎng)理交通學(xué)院組織畢業(yè)生黨員開(kāi)展考研就業(yè)分享會(huì)
發(fā)布日期:2024年05月27日 來(lái)源:

“學(xué)長(zhǎng),請(qǐng)問(wèn)當(dāng)時(shí)你是如何確定自己是考研還是就業(yè)呢?”“我認(rèn)為,考研還是就業(yè)都是人生的一種選擇路徑,沒(méi)有孰優(yōu)孰劣之分,在做出決策時(shí),應(yīng)充分考慮自己的實(shí)際情況和未來(lái)發(fā)展規(guī)劃……”5月26日,長(zhǎng)沙理工大學(xué)交通運(yùn)輸工程學(xué)院各本科生黨支部“我為群眾辦實(shí)事”主題黨日活動(dòng)正在火熱進(jìn)行,畢業(yè)生黨員們就自身考研就業(yè)經(jīng)歷與學(xué)弟學(xué)妹們進(jìn)行了分享交流。

已推免至同濟(jì)大學(xué)讀研的李瑞敏從學(xué)習(xí)能力養(yǎng)成、創(chuàng)新能力培養(yǎng)、學(xué)科競(jìng)賽參與三個(gè)方面為立志推免保研的同學(xué)提供了方向和思路,她強(qiáng)調(diào)要腳踏實(shí)地,學(xué)會(huì)與焦慮共處,走好大學(xué)學(xué)習(xí)生活每一步;已成功考研至西南交通大學(xué)的李思晴以自身經(jīng)歷為例,圍繞考研動(dòng)機(jī)、擇校經(jīng)驗(yàn)、學(xué)習(xí)方法與計(jì)劃、復(fù)試技巧等幾方面給同學(xué)們提出了考研的寶貴意見(jiàn),她強(qiáng)調(diào),備戰(zhàn)考研要目標(biāo)明確,同時(shí)保持好自身復(fù)習(xí)節(jié)奏,要注重勞逸結(jié)合,保持積極心態(tài),方可事半功倍……

已簽約浙江交工集團(tuán)的鄭淇真結(jié)合自己在多個(gè)公司、不同崗位的企業(yè)實(shí)習(xí)經(jīng)歷,從總體就業(yè)形勢(shì)、求職準(zhǔn)備、簡(jiǎn)歷制作和面試技巧四個(gè)方面深入分享了在實(shí)習(xí)和求職過(guò)程中的心得體會(huì),鼓勵(lì)大家要勇于嘗試、敢于探索,勇敢跳出“舒適圈”,將自我價(jià)值實(shí)現(xiàn)與祖國(guó)需要相結(jié)合,到祖國(guó)最需要的地方去,為交通強(qiáng)國(guó)建設(shè)建功立業(yè);此外,幾名考公同志結(jié)合自身備考經(jīng)驗(yàn),從考情分析、選崗建議、復(fù)習(xí)計(jì)劃等多角度就各類公務(wù)員考試進(jìn)行了分享等。

會(huì)后,各支部均設(shè)立了交流環(huán)節(jié),對(duì)同學(xué)們的疑問(wèn)進(jìn)行逐一解答。大二學(xué)生張思思表示:“通過(guò)此次分享會(huì),我明白了人生的選擇有很多,我會(huì)努力破除固有的觀念,進(jìn)一步審視自我、剖析自我,明確自身發(fā)展需求、明確社會(huì)發(fā)展大勢(shì)、明確黨和國(guó)家需要,在了解中明晰個(gè)人職業(yè)規(guī)劃,堅(jiān)定信心、勇往直前!”

在畢業(yè)季來(lái)臨之際,交通學(xué)院持續(xù)加強(qiáng)畢業(yè)生黨員教育管理,通過(guò)開(kāi)展理論學(xué)習(xí)、黨紀(jì)教育、志愿服務(wù)、主題座談、經(jīng)驗(yàn)分享等各類畢業(yè)教育活動(dòng),引導(dǎo)畢業(yè)生黨員站好畢業(yè)離校前“最后一班崗”,自覺(jué)增強(qiáng)政治意識(shí)、愛(ài)校情結(jié)和感恩意識(shí),服務(wù)好學(xué)生黨員在?!白詈笠还铩?/span>。

    (文/圖 伍思豪 一審/肖敏強(qiáng) 二審/汪招霞 三審/任志祥)



?? 0
`; document.fullscreenElement.appendChild(shadowWrapper); var container = wrapper.getElementById('container'); let expandBtn = wrapper.getElementById('expand-button'); let msgInput = wrapper.getElementById('text-input'); let sendBtn = wrapper.getElementById('send-button'); let closeBtn = wrapper.getElementById('close-btn'); let expanded = true; function expand() { if (expanded) { expandBtn.innerText = '>' sendBtn.style.display = 'none'; msgInput.classList.remove('expand'); } else { expandBtn.innerText = '<'; 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"; shadowWrapper.ontouchstart = (e) => { e.stopPropagation() } let wrapper; try { wrapper = AttachShadow(shadowWrapper, { mode: "open" }); wrapper.addEventListener('keydown', (e) => e.stopPropagation()) } catch (e) { console.error(e); } this.shadowWrapper = shadowWrapper; this.wrapper = wrapper; wrapper.innerHTML = `
VideoTogether
房間
密碼
連接文字聊天服務(wù)器中... 缺少中文語(yǔ)音包
檢測(cè)視頻中...
下載中,不要關(guān)閉頁(yè)面 下載完成
視頻音量
通話音量

IOS不支持音量調(diào)節(jié)

`; (document.body || document.documentElement).appendChild(shadowWrapper); wrapper.querySelector("#videoTogetherMinimize").onclick = () => { this.Minimize() } wrapper.querySelector("#videoTogetherMaximize").onclick = () => { this.Maximize() } ["", "webkit"].forEach(prefix => { document.addEventListener(prefix + "fullscreenchange", (event) => { if (document.fullscreenElement || document.webkitFullscreenElement) { hide(this.videoTogetherFlyPannel); hide(this.videoTogetherSamllIcon); } else { if (this.minimized) { this.Minimize(); } else { this.Maximize(); } } }); }); wrapper.querySelector("#textMessageInput").addEventListener("keyup", e => { if (e.key == "Enter") { wrapper.querySelector("#textMessageSend").click(); } }); wrapper.querySelector("#textMessageSend").onclick = async () => { extension.currentSendingMsgId = generateUUID(); WS.sendTextMessage(extension.currentSendingMsgId, select("#textMessageInput").value); } this.lobbyBtnGroup = wrapper.querySelector("#lobbyBtnGroup"); this.createRoomButton = wrapper.querySelector('#videoTogetherCreateButton'); this.joinRoomButton = wrapper.querySelector("#videoTogetherJoinButton"); this.roomButtonGroup = wrapper.querySelector('#roomButtonGroup'); this.exitButton = wrapper.querySelector("#videoTogetherExitButton"); this.callBtn = wrapper.querySelector("#callBtn"); this.callBtn.onclick = () => Voice.join("", window.videoTogetherExtension.roomName); this.helpButton = wrapper.querySelector("#videoTogetherHelpButton"); this.audioBtn = wrapper.querySelector("#audioBtn"); this.micBtn = wrapper.querySelector("#micBtn"); this.videoVolume = wrapper.querySelector("#videoVolume"); this.callVolumeSlider = wrapper.querySelector("#callVolume"); this.callErrorBtn = wrapper.querySelector("#callErrorBtn"); this.easyShareCopyBtn = wrapper.querySelector("#easyShareCopyBtn"); this.textMessageChat = wrapper.querySelector("#textMessageChat"); this.textMessageConnecting = wrapper.querySelector("#textMessageConnecting"); this.textMessageConnectingStatus = wrapper.querySelector("#textMessageConnectingStatus"); this.zhcnTtsMissing = wrapper.querySelector("#zhcnTtsMissing"); this.downloadBtn = wrapper.querySelector("#downloadBtn"); hide(this.downloadBtn); this.confirmDownloadBtn = wrapper.querySelector("#confirmDownloadBtn") this.confirmDownloadBtn.onclick = () => { if (extension.downloadM3u8UrlType == "video") { extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_video_download") console.log(extension.downloadM3u8Url, extension.downloadM3u8UrlType) sendMessageToTop(MessageType.SetStorageValue, { key: "PublicNextDownload", value: { filename: document.title + '.mp4', url: extension.downloadM3u8Url } }); const a = document.createElement("a"); a.href = extension.downloadM3u8Url; a.target = "_blank"; a.download = document.title + ".mp4"; a.click(); return; } extension.Fetch(extension.video_together_host + "/beta/counter?key=confirm_m3u8_download") isDownloading = true; const m3u8url = extension.downloadM3u8Url sendMessageTo(extension.m3u8PostWindows[extension.GetM3u8WindowId(m3u8url)], MessageType.StartDownload, { m3u8Url: m3u8url, m3u8Content: extension.GetM3u8Content(m3u8url), urls: extension.GetAllM3u8SegUrls(m3u8url), title: document.title, pageUrl: window.location.href }); hide(this.confirmDownloadBtn); show(select("#downloadProgress")); } this.downloadBtn.onclick = () => { setInterval(() => { if (isDownloading) { return; } if (extension.downloadM3u8Url != undefined) { show(this.confirmDownloadBtn); select('#downloadVideoInfo').innerText = getDurationStr(extension.downloadDuration); } else { hide(this.confirmDownloadBtn); select('#downloadVideoInfo').innerText = "檢測(cè)視頻中..." } }, 1000); inDownload = true; this.inputRoomName.value = "download_" + generateUUID(); this.createRoomButton.click() hide(select('.vt-modal-footer')) hide(select('#mainPannel')) show(select('#downloadPannel')) } this.easyShareCopyBtn.onclick = async () => { try { if (isWeb()) { await navigator.clipboard.writeText(extension.linkWithMemberState(window.location, extension.RoleEnum.Member, false)) } else { await navigator.clipboard.writeText("點(diǎn)擊鏈接,和我一起看吧:, 如果打不開(kāi)可以嘗試備用鏈接:" .replace("", extension.generateEasyShareLink()) .replace("", extension.generateEasyShareLink(true))); } popupError("復(fù)制成功,快去分享吧"); } catch { popupError("復(fù)制失敗"); } } 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("#videoTogetherRoomPdIpt"); this.inputRoomNameLabel = wrapper.querySelector('#videoTogetherRoomNameLabel'); this.inputRoomPasswordLabel = wrapper.querySelector("#videoTogetherRoomPasswordLabel"); this.videoTogetherHeader = wrapper.querySelector("#videoTogetherHeader"); this.videoTogetherFlyPannel = wrapper.getElementById("videoTogetherFlyPannel"); this.videoTogetherSamllIcon = wrapper.getElementById("videoTogetherSamllIcon"); this.volume = 1; this.statusText = wrapper.querySelector("#videoTogetherStatusText"); this.InLobby(true); this.Init(); setInterval(() => { this.ShowPannel(); }, 1000); } try { document.querySelector("#videoTogetherLoading").remove() } catch { } } ShowTxtMsgTouchPannel() { try { function exitFullScreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { /* Safari */ document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { /* Firefox */ document.mozCancelFullScreen(); } } exitFullScreen(); } catch { } try { this.txtMsgTouchPannel.remove(); } catch { } this.txtMsgTouchPannel = document.createElement('div'); let touch = this.txtMsgTouchPannel; touch.id = "videoTogetherTxtMsgTouch"; touch.style.width = "100%"; touch.style.height = "100%"; touch.style.position = "fixed"; touch.style.top = "0"; touch.style.left = "0"; touch.style.zIndex = "2147483647"; touch.style.background = "#fff"; touch.style.display = "flex"; touch.style.justifyContent = "center"; touch.style.alignItems = "center"; touch.style.padding = "0px"; touch.style.flexDirection = "column"; touch.style.lineHeight = "40px"; AttachShadow(this.txtMsgTouchPannel, { mode: "open" }) touch.addEventListener('click', function () { windowPannel.enableSpeechSynthesis(); document.body.removeChild(touch); windowPannel.txtMsgTouchPannel = undefined; }); document.body.appendChild(touch); this.setTxtMsgTouchPannelText("VideoTogether: 您有一條新消息,點(diǎn)擊屏幕接收"); } setTxtMsgInterface(type) { hide(this.textMessageChat); hide(this.textMessageConnecting); hide(this.textMessageConnectingStatus); hide(this.zhcnTtsMissing); if (type == 0) { } if (type == 1) { show(this.textMessageChat); } if (type == 2) { show(this.textMessageConnecting); this.textMessageConnectingStatus.innerText = "連接文字聊天服務(wù)器中..." show(this.textMessageConnectingStatus); } if (type == 3) { show(this.textMessageConnecting); show(this.zhcnTtsMissing); } if (type == 4) { show(this.textMessageConnecting); this.textMessageConnectingStatus.innerText = "文字聊天已關(guān)閉" show(this.textMessageConnectingStatus); } } enableSpeechSynthesis() { if (!extension.speechSynthesisEnabled) { try { extension.gotTextMsg("", "", true); extension.speechSynthesisEnabled = true; } catch { } } } setTxtMsgTouchPannelText(s) { let span = document.createElement('span'); span.style.fontSize = "40px"; span.style.lineHeight = "40px"; span.style.color = "black"; span.style.overflowWrap = "break-word"; span.style.textAlign = "center"; span.textContent = s; this.txtMsgTouchPannel.shadowRoot.appendChild(span); let voiceSelect = document.createElement('select'); this.voiceSelect = voiceSelect; voiceSelect.onclick = (e) => { e.stopPropagation(); } let label = span.cloneNode(true); label.textContent = "你可以在下方選擇朗讀信息的語(yǔ)音:"; 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; hide(this.downloadBtn) } InLobby(init = false) { if (!init) { this.Maximize(); } this.inputRoomName.disabled = false; this.inputRoomPasswordLabel.style.display = "inline-block"; this.inputRoomPassword.style.display = "inline-block"; this.inputRoomName.placeholder = "請(qǐng)輸入房間名" show(this.lobbyBtnGroup); hide(this.roomButtonGroup); hide(this.easyShareCopyBtn); this.setTxtMsgInterface(0); dsply(this.downloadBtn, downloadEnabled()) this.isInRoom = false; } CreateRoomButtonOnClick() { this.Maximize(); let roomName = this.inputRoomName.value; let password = this.inputRoomPassword.value; window.videoTogetherExtension.CreateRoom(roomName, password); } JoinRoomButtonOnClick() { this.Maximize(); let roomName = this.inputRoomName.value; let password = this.inputRoomPassword.value; window.videoTogetherExtension.JoinRoom(roomName, password); } HelpButtonOnClick() { this.Maximize(); let url = 'https://2gether.video/guide/qa.html'; if (vtRuntime == "website") { url = "https://2gether.video/guide/website_qa.html" } window.open(url, '_blank'); } UpdateStatusText(text, color) { this.statusText.innerHTML = text; this.statusText.style.color = color; } } class VideoModel { constructor(id, duration, activatedTime, refreshTime, priority = 0) { this.id = id; this.duration = duration; this.activatedTime = activatedTime; this.refreshTime = refreshTime; this.priority = priority; } } let MessageType = { ActivatedVideo: 1, ReportVideo: 2, SyncMemberVideo: 3, SyncMasterVideo: 4, UpdateStatusText: 5, JumpToNewPage: 6, GetRoomData: 7, ChangeVoiceVolume: 8, ChangeVideoVolume: 9, FetchRequest: 13, FetchResponse: 14, SetStorageValue: 15, SyncStorageValue: 16, ExtensionInitSuccess: 17, SetTabStorage: 18, SetTabStorageSuccess: 19, UpdateRoomRequest: 20, CallScheduledTask: 21, RoomDataNotification: 22, UpdateMemberStatus: 23, TimestampV2Resp: 24, // EasyShareCheckSucc: 25, FetchRealUrlReq: 26, FetchRealUrlResp: 27, FetchRealUrlFromIframeReq: 28, FetchRealUrlFromIframeResp: 29, SendTxtMsg: 30, GotTxtMsg: 31, StartDownload: 32, DownloadStatus: 33, UpdateM3u8Files: 1001, SaveIndexedDb: 2001, ReadIndexedDb: 2002, SaveIndexedDbResult: 2003, ReadIndexedDbResult: 2004, RegexMatchKeysDb: 2005, RegexMatchKeysDbResult: 2006, DeleteFromIndexedDb: 2007, DeleteFromIndexedDbResult: 2008, StorageEstimate: 2009, StorageEstimateResult: 2010, ReadIndexedDbSw: 2011, ReadIndexedDbSwResult: 2012, //2013 used IosStorageSet: 3001, IosStorageSetResult: 3002, IosStorageGet: 3003, IosStorageGetResult: 3004, IosStorageDelete: 3005, IosStorageDeleteResult: 3006, IosStorageUsage: 3007, IosStorageUsageResult: 3008, IosStorageCompact: 3009, IosStorageDeletePrefix: 3010, IosStorageDeletePrefixResult: 3011, } let VIDEO_EXPIRED_SECOND = 10 class VideoWrapper { set currentTime(v) { this.currentTimeSetter(v); } get currentTime() { return this.currentTimeGetter(); } set playbackRate(v) { this.playbackRateSetter(v); } get playbackRate() { return this.playbackRateGetter(); } constructor(play, pause, paused, currentTimeGetter, currentTimeSetter, duration, playbackRateGetter, playbackRateSetter) { this.play = play; this.pause = pause; this.paused = paused; this.currentTimeGetter = currentTimeGetter; this.currentTimeSetter = currentTimeSetter; this.duration = duration; this.playbackRateGetter = playbackRateGetter; this.playbackRateSetter = playbackRateSetter; } } class VideoTogetherExtension { constructor() { this.RoleEnum = { Null: 1, Master: 2, Member: 3, } this.cspBlockedHost = {}; this.video_together_host = 'https://vt.panghair.com:5000/'; this.video_together_main_host = 'https://vt.panghair.com:5000/'; this.video_together_backup_host = 'https://api.chizhou.in/'; this.video_tag_names = ["video", "bwp-video", "fake-iframe-video"] this.timer = 0 this.roomName = "" this.roomPassword = "" this.role = this.RoleEnum.Null this.url = "" this.duration = undefined this.waitForLoadding = false; this.playAfterLoadding = false; this.minTrip = 1e9; this.timeOffset = 0; this.lastScheduledTaskTs = 0; this.httpSucc = false; this.activatedVideo = undefined; this.tempUser = generateTempUserId(); this.version = '1707141762'; this.isMain = (window.self == window.top); this.UserId = undefined; this.callbackMap = new Map; this.allLinksTargetModified = false; this.voiceVolume = null; this.videoVolume = null; this.m3u8Files = {}; this.m3u8DurationReCal = {}; this.m3u8UrlTestResult = {}; this.hasCheckedM3u8Url = {}; this.m3u8PostWindows = {}; this.m3u8MediaUrls = {}; this.currentM3u8Url = undefined; this.ctxMemberCount = 0; this.downloadSpeedMb = 0; this.downloadPercentage = 0; this.currentSendingMsgId = null; this.isIos = undefined; this.speechSynthesisEnabled = false; // we need a common callback function to deal with all message this.SetTabStorageSuccessCallback = () => { }; document.addEventListener("securitypolicyviolation", (e) => { let host = (new URL(e.blockedURI)).host; this.cspBlockedHost[host] = true; }); try { this.CreateVideoDomObserver(); } catch { } this.timer = setInterval(() => this.ScheduledTask(true), 2 * 1000); this.videoMap = new Map(); window.addEventListener('message', message => { if (message.data.context) { this.tempUser = message.data.context.tempUser; this.videoTitle = message.data.context.videoTitle; this.voiceStatus = message.data.context.voiceStatus; this.timeOffset = message.data.context.timeOffset; this.ctxRole = message.data.context.ctxRole; this.ctxMemberCount = message.data.context.ctxMemberCount; this.ctxWsIsOpen = message.data.context.ctxWsIsOpen; // sub frame has 2 storage data source, top frame or extension.js in this frame // this 2 data source should be same. window.VideoTogetherStorage = message.data.context.VideoTogetherStorage; } this.processReceivedMessage(message.data.type, message.data.data, message); }); try { navigator.serviceWorker.addEventListener('message', (message) => { console.log(`Received a message from service worker: ${event.data}`); this.processReceivedMessage(message.data.type, message.data.data, message); }); } catch { }; // if some element's click be invoked frequenctly, a lot of http request will be sent // window.addEventListener('click', message => { // setTimeout(this.ScheduledTask.bind(this), 200); // }) if (this.isMain) { try { try { this.RecoveryState(); } catch { } this.EnableDraggable(); setTimeout(() => { let allDoms = document.querySelectorAll("*"); for (let i = 0; i < 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 '' } 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: "超時(shí)" }); } } finally { this.callbackMap.delete(id); } }, 20000); }); } try { if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.fetch))) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); return await window.fetch(url, { method: method, body: data == null ? undefined : JSON.stringify(data), signal: controller.signal }); } else { GetNativeFunction(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); return await Global.NativeFetch.call(window, url, { method: method, body: data == null ? undefined : JSON.stringify(data), signal: controller.signal }); } } catch (e) { const host = new URL(extension.video_together_host); const requestUrl = new URL(url); if (host.hostname == requestUrl.hostname) { extension.httpSucc = false; } throw e; } } async ForEachVideo(func) { try { if (window.location.hostname.endsWith("iqiyi.com")) { let video = document.querySelector('.iqp-player-videolayer-inner > video'); if (video != null) { video.VideoTogetherChoosed = true; try { await func(video) } catch { }; } } // disneyplus if (window.location.hostname.endsWith("disneyplus.com")) { try { let ff = document.querySelector('.ff-10sec-icon'); let rr = document.querySelector('.rwd-10sec-icon'); let video = document.querySelector('video'); if (ff && rr && video) { if (!video.videoTogetherVideoWrapper) { video.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = video.videoTogetherVideoWrapper; videoWrapper.play = async () => await video.play(); videoWrapper.pause = async () => await video.pause(); videoWrapper.paused = video.paused videoWrapper.currentTimeGetter = () => video.currentTime; videoWrapper.currentTimeSetter = (v) => { let isFf = v > video.currentTime; let d = Math.abs(v - video.currentTime); let clickTime = parseInt(d / 10); if (clickTime > 0) { console.log(clickTime); } for (let i = 0; i < 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) { } } // 百度網(wǎng)盤 if (window.location.host.includes('pan.baidu.com')) { if (!this.BaiduPanPlayer) { try { if (document.querySelector('.vjs-controls-enabled').player != undefined) { this.BaiduPanPlayer = document.querySelector('.vjs-controls-enabled').player; } } catch { } } if (this.BaiduPanPlayer) { if (!this.BaiduPanPlayer.videoTogetherVideoWrapper) { this.BaiduPanPlayer.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = this.BaiduPanPlayer.videoTogetherVideoWrapper; videoWrapper.play = async () => await this.BaiduPanPlayer.play(); videoWrapper.pause = async () => await this.BaiduPanPlayer.pause(); videoWrapper.paused = this.BaiduPanPlayer.paused(); videoWrapper.currentTimeGetter = () => this.BaiduPanPlayer.currentTime(); videoWrapper.currentTimeSetter = (v) => this.BaiduPanPlayer.currentTime(v); videoWrapper.duration = this.BaiduPanPlayer.duration(); videoWrapper.playbackRateGetter = () => this.BaiduPanPlayer.playbackRate(); videoWrapper.playbackRateSetter = (v) => this.BaiduPanPlayer.playbackRate(v); await func(videoWrapper); } } } catch (e) { } try { // 騰訊視頻 if (window.__PLAYER__ != undefined) { if (window.__PLAYER__.videoTogetherVideoWrapper == undefined) { window.__PLAYER__.videoTogetherVideoWrapper = new VideoWrapper(); } let videoWrapper = window.__PLAYER__.videoTogetherVideoWrapper; videoWrapper.play = async () => await window.__PLAYER__.corePlayer.play(); videoWrapper.pause = async () => await window.__PLAYER__.corePlayer.pause(); videoWrapper.paused = window.__PLAYER__.paused; videoWrapper.currentTimeGetter = () => window.__PLAYER__.currentVideoInfo.playtime; videoWrapper.currentTimeSetter = (v) => { if (!videoWrapper.videoTogetherPaused) { window.__PLAYER__.seek(v) } }; videoWrapper.duration = window.__PLAYER__.currentVideoInfo.duration; videoWrapper.playbackRateGetter = () => window.__PLAYER__.playbackRate; videoWrapper.playbackRateSetter = (v) => window.__PLAYER__.playbackRate = v; await func(videoWrapper); } } catch (e) { }; this.video_tag_names.forEach(async tag => { let videos = document.getElementsByTagName(tag); for (let i = 0; i < videos.length; i++) { try { try { if (videos[i].VideoTogetherDisabled) { continue; } } catch { }; try { if (window.location.hostname.endsWith('bilibili.com')) { if (!!videos[i].closest('div.video-page-card-small') || !!videos[i].closest('div.feed-card')) { // this is a thumbnail video continue } } } catch { } await func(videos[i]); } catch (e) { console.error(e) }; } }); } sendMessageToSonWithContext(type, data) { if (this.isMain) { this.ctxRole = this.role; } 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; } GetM3u8WindowId(m3u8Url) { let windowId = undefined; for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { if (m3u8Url == m3u8.m3u8Url) { windowId = id; } }) } return windowId; } UrlRequest(m3u8Url, idx, origin) { for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { if (m3u8Url == m3u8.m3u8Url) { let urls = extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url); let url = urls[idx]; sendMessageTo(this.m3u8PostWindows[id], MessageType.FetchRealUrlReq, { url: url, origin: origin }); } }) } } async testM3u8OrVideoUrl(testUrl) { const onsecuritypolicyviolation = (e) => { if (e.blockedURI == testUrl) { // m3u8 can always be fetched, because hls.js this.m3u8UrlTestResult[testUrl] = 'video' } } document.addEventListener("securitypolicyviolation", onsecuritypolicyviolation) if (this.m3u8UrlTestResult[testUrl] != undefined) { return this.m3u8UrlTestResult[testUrl]; } function limitStream(stream, limit) { const reader = stream.getReader(); let bytesRead = 0; return new ReadableStream({ async pull(controller) { const { value, done } = await reader.read(); if (done || bytesRead >= limit) { controller.close(); return; } bytesRead += value.byteLength; controller.enqueue(value); }, cancel(reason) { reader.cancel(reason); } }); } return new Promise((res, rej) => { const rtnType = (tp) => { if (this.m3u8UrlTestResult[testUrl] == undefined) { this.m3u8UrlTestResult[testUrl] = tp } res(this.m3u8UrlTestResult[testUrl]) } const abortController = new AbortController(); VideoTogetherFetch(testUrl, { signal: abortController.signal }).then(response => { const contentType = response.headers.get('Content-Type') if (contentType.startsWith('video/')) { rtnType('video'); } const limitedStream = limitStream(response.body, 1024); // Limit to 1024 bytes return new Response(limitedStream, { headers: response.headers }); }).then(r => r.text()) .then(async txt => { abortController.abort(); if (isM3U8(txt)) { rtnType('m3u8'); } else { rtnType('video'); } }).catch(e => { if (testUrl.startsWith('blob')) { rtnType('unknown'); } else { rtnType('video'); } }).finally(() => { document.removeEventListener("securitypolicyviolation", onsecuritypolicyviolation) }) }) } // download GetAllM3u8SegUrls(m3u8Url) { for (let id in this.m3u8Files) { for (let mid in this.m3u8Files[id]) { let m3u8 = this.m3u8Files[id][mid] if (m3u8Url == m3u8.m3u8Url) { return extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url); } } } } // end of download UpdateStatusText(text, color) { if (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; try { let d = NaN; let selected = null; for (let id in this.m3u8Files) { this.m3u8Files[id].forEach(m3u8 => { // here m3u8Url may be empty, may caused by the new response // from limitedstream, but we have a new fetch after that, // so we can always get the correct url. if (isNaN(d) || Math.abs(data.duration - m3u8.duration) <= d) { d = Math.abs(data.duration - m3u8.duration); selected = m3u8; } return; }) } if (d < 3 || d / data.duration < 0.03) { m3u8Url = selected.m3u8Url; } } catch { } if (data.m3u8Url == undefined) { data.m3u8Url = m3u8Url; } else { }// data.m3u8Url may be a video file if (data.m3u8UrlType == 'video') { this.downloadM3u8Url = data.m3u8Url; this.downloadM3u8UrlType = 'video'; this.downloadDuration = data.duration; } else { if (m3u8Url != undefined) { this.downloadM3u8Url = m3u8Url; this.downloadDuration = data.duration; this.downloadM3u8UrlType = 'm3u8'; // video or other } else { this.downloadM3u8Url = undefined; this.downloadDuration = undefined; } } if (!isEasyShareEnabled()) { data.m3u8Url = ""; } try { if (!isEmpty(data.m3u8Url) && isEasyShareEnabled()) { this.currentM3u8Url = data.m3u8Url; show(windowPannel.easyShareCopyBtn); } else { this.currentM3u8Url = undefined; if (isWeb()) { show(windowPannel.easyShareCopyBtn); } else { 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); if (newUrl.hash != "") { currentUrl.hash = ""; newUrl.hash = ""; if (currentUrl.href == newUrl.href) { extension.url = data.url; // window.location.reload();// for hash change } } break; case MessageType.ChangeVideoVolume: this.ForEachVideo(video => { video.volume = data.volume; }); this.sendMessageToSonWithContext(type, data); break; case MessageType.FetchResponse: { try { this.callbackMap.get(data.id)(data); } catch { }; break; } case MessageType.SyncStorageValue: { const firstSync = (window.VideoTogetherSettingEnabled == undefined) window.VideoTogetherStorage = data; if (!this.isMain) { return; } try { if (window.VideoTogetherStorage.PublicNextDownload.url == window.location.href && this.HasDownload != true) { const a = document.createElement("a"); a.href = window.VideoTogetherStorage.PublicNextDownload.url; a.download = window.VideoTogetherStorage.PublicNextDownload.filename; a.click(); this.HasDownload = true; } } catch { } try { if (!this.RecoveryStateFromTab) { this.RecoveryStateFromTab = true; this.RecoveryState() } } catch (e) { }; try { if (data.PublicMessageVoice != null) { windowPannel.voiceSelect.value = data.PublicMessageVoice; } } catch { }; if (!window.videoTogetherFlyPannel.disableDefaultSize && firstSync) { if (data.MinimiseDefault) { window.videoTogetherFlyPannel.Minimize(true); } else { window.videoTogetherFlyPannel.Maximize(true); } } if (typeof (data.PublicUserId) != 'string' || data.PublicUserId.length < 5) { sendMessageToTop(MessageType.SetStorageValue, { key: "PublicUserId", value: generateUUID() }); } try { if (firstSync) { 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) { } try { dsply(select('#downloadBtn'), downloadEnabled() && !windowPannel.isInRoom) } catch { } 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 { function calculateM3U8Duration(textContent) { let totalDuration = 0; const lines = textContent.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith('#EXTINF:')) { if (i + 1 >= lines.length || lines[i + 1].startsWith('#')) { continue; } let durationLine = lines[i]; let durationParts = durationLine.split(':'); if (durationParts.length > 1) { let durationValue = durationParts[1].split(',')[0]; let duration = parseFloat(durationValue); if (!isNaN(duration)) { totalDuration += duration; } } } } return totalDuration; } const cyrb53 = (str, seed = 0) => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < 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)}`; } if (this.m3u8DurationReCal[m3u8.m3u8Url] == undefined) { this.m3u8DurationReCal[m3u8.m3u8Url] = calculateM3U8Duration(m3u8.m3u8Content); } m3u8.duration = this.m3u8DurationReCal[m3u8.m3u8Url]; } catch { } }) this.m3u8Files[data['id']] = data['m3u8Files']; this.m3u8PostWindows[data['id']] = _msg.source; break; } case MessageType.FetchRealUrlReq: { console.log(data); if (realUrlCache[data.url] == undefined) { const controller = new AbortController(); let r = await Fetch(data.url, { method: "GET", signal: controller.signal }); controller.abort(); realUrlCache[data.url] = r.url; } 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; } case MessageType.ReadIndexedDbSw: { const result = await readFromIndexedDB(data.table, data.key); data.data = result navigator.serviceWorker.controller.postMessage({ source: "VideoTogether", type: 2012, data: data }); break; } case MessageType.StartDownload: { startDownload(data.m3u8Url, data.m3u8Content, data.urls, data.title, data.pageUrl); setInterval(() => { sendMessageToTop(MessageType.DownloadStatus, { downloadSpeedMb: this.downloadSpeedMb, downloadPercentage: this.downloadPercentage }) }, 1000) break; } case MessageType.DownloadStatus: { extension.downloadSpeedMb = data.downloadSpeedMb; extension.downloadPercentage = data.downloadPercentage; if (extension.downloadPercentage == 100) { if (this.downloadM3u8Completed != true) { this.downloadM3u8Completed = true; extension.Fetch(extension.video_together_host + "/beta/counter?key=download_m3u8_completed") } hide(select("#downloadingAlert")) show(select("#downloadCompleted")) } select("#downloadStatus").innerText = extension.downloadPercentage + "% " select("#downloadSpeed").innerText = extension.downloadSpeedMb.toFixed(2) + "MB/s" select("#downloadProgressBar").value = extension.downloadPercentage break; } default: // console.info("unhandled message:", type, data) break; } } openAllLinksInSelf() { let hrefs = document.getElementsByTagName("a"); for (let i = 0; i < hrefs.length; i++) { hrefs[i].target = "_self"; } } async RunWithRetry(func, count) { for (let i = 0; i < 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("請(qǐng)輸入房間名") 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("請(qǐng)注意調(diào)試模式已開(kāi)啟, 您的隱私很有可能會(huì)被泄漏"); (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 (!getEnableTextMessage()) { windowPannel.setTxtMsgInterface(4); } else 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 { // TODO // if (this.video_together_host == this.video_together_backup_host) { // this.SyncTimeWithServer(this.video_together_main_host); // } } } catch { }; } try { switch (this.role) { case this.RoleEnum.Null: return; case this.RoleEnum.Master: { if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) { let state = this.GetRoomState(""); 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("頁(yè)面沒(méi)有視頻"); } 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("該視頻無(wú)法同步"); } 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("請(qǐng)?jiān)谔D(zhuǎn)后再次加入"); } } } 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("頁(yè)面沒(méi)有視頻"); } 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) { try { if (this.isMain) { useMobileStyle(videoDom); } } catch { } 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; let m3u8UrlType; try { let nativeSrc = videoDom.src; if (nativeSrc == "" || nativeSrc == undefined) { nativeSrc = videoDom.querySelector('source').src; } nativeSrc = new URL(nativeSrc, window.location).href if (nativeSrc.startsWith('http')) { m3u8Url = nativeSrc; } this.testM3u8OrVideoUrl(nativeSrc).then(r => { if (r == 'm3u8' && this.hasCheckedM3u8Url[nativeSrc] != true) { fetch(nativeSrc).then(r => r.text()).then(m3u8Content => { if (isMasterM3u8(m3u8Content)) { const mediaM3u8Url = getFirstMediaM3U8(m3u8Content, nativeSrc); fetch(mediaM3u8Url).then(r => r.text()).then(() => { this.hasCheckedM3u8Url[nativeSrc] = true; }) } else { this.hasCheckedM3u8Url[nativeSrc] = true; } } ) } }) m3u8UrlType = this.m3u8UrlTestResult[nativeSrc] } catch { }; sendMessageToTop(MessageType.UpdateRoomRequest, { name: data.roomName, password: data.password, url: data.link, playbackRate: videoDom.playbackRate, currentTime: videoDom.currentTime, paused: paused, duration: videoDom.duration, localTimestamp: this.getLocalTimestamp(), m3u8Url: m3u8Url, m3u8UrlType: m3u8UrlType }) } linkWithoutState(link) { let url = new URL(link); url.searchParams.delete("VideoTogetherUrl"); url.searchParams.delete("VideoTogetherRoomName"); url.searchParams.delete("VideoTogetherRole"); url.searchParams.delete("VideoTogetherPassword"); url.searchParams.delete("VideoTogetherTimestamp"); return url.toString(); } GetRoomState(link) { if (inDownload) { return {}; } if (this.role == this.RoleEnum.Null) { return {}; } let voice = Voice.status; if (voice == VoiceStatus.CONNECTTING) { try { voice = window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherVoice; } catch { voice = VoiceStatus.STOP; } } return { VideoTogetherUrl: link, VideoTogetherRoomName: this.roomName, VideoTogetherPassword: this.password, VideoTogetherRole: this.role, VideoTogetherTimestamp: Date.now() / 1000, VideoTogetherVoice: voice, VideoVolume: this.getVideoVolume(), VoiceVolume: this.getVoiceVolume() } } SaveStateToSessionStorageWhenSameOrigin(link) { if (inDownload) { return false; } try { let sameOrigin = false; if (link != "") { let url = new URL(link); let currentUrl = new URL(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, newRole = undefined, expire = true) { let url = new URL(link); let tmpSearch = url.search; url.search = ""; url.searchParams.set("VideoTogetherUrl", link); url.searchParams.set("VideoTogetherRoomName", this.roomName); url.searchParams.set("VideoTogetherPassword", this.password); url.searchParams.set("VideoTogetherRole", newRole ? newRole : this.role); url.searchParams.set("VideoTogetherTimestamp", expire ? Date.now() / 1000 : 1e10); let urlStr = url.toString(); if (tmpSearch.length > 1) { urlStr = urlStr + "&" + tmpSearch.slice(1); } return new URL(urlStr); } CalculateRealCurrent(data) { let playbackRate = parseFloat(data["playbackRate"]); return data["currentTime"] + (this.getLocalTimestamp() - data["lastUpdateClientTime"]) * (isNaN(playbackRate) ? 1 : playbackRate); } GetDisplayTimeText() { let date = new Date(); return date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); } async SyncMemberVideo(data, videoDom) { try { if (this.isMain) { useMobileStyle(videoDom); } } catch { } if (this.lastSyncMemberVideo + 1 > Date.now() / 1000) { return; } this.lastSyncMemberVideo = Date.now() / 1000; let room = data.room; sendMessageToTop(MessageType.GetRoomData, room); // useless this.duration = room["duration"]; // useless if (videoDom == undefined) { throw new Error("沒(méi)有視頻"); } 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("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } } await videoDom.play(); if (videoDom.paused) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } catch (e) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } } } if (videoDom.playbackRate != room["playbackRate"]) { try { videoDom.playbackRate = parseFloat(room["playbackRate"]); } catch (e) { } } if (isNaN(videoDom.duration)) { throw new Error("請(qǐng)手動(dòng)點(diǎn)擊播放"); } 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("請(qǐng)輸入房間名") 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; } } try { if (window.location.hostname == 'yiyan.baidu.com') { GetNativeFunction(); window.Element.prototype.attachShadow = Global.NativeAttachShadow; console.log("Use native attachShadow in yiyan") } } catch { } // 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 { }})()">
版權(quán)所有? 長(zhǎng)沙理工大學(xué)交通運(yùn)輸工程學(xué)院
地址:湖南省長(zhǎng)沙市天心區(qū)萬(wàn)家麗南路二段960號(hào)
郵編:410114電話:0731-85258575
黄色在线网站wwwwww,亚洲一区免费观看,疯狂丑小鸭2,特黄毛片官网免费看