514-613-1276
contact@mengchenghui.com
工作时间:周一至周五10:00-17:00
热搜: 房产 留学 医疗

[电脑/电器相关] 拍立得fujifilm instax mini 9

[复制链接]
251 0
patrickjiangc 发表于 2023-7-13 17:46:49 | 只看该作者 |只看大图 |阅读模式 打印 上一主题 下一主题 来自: 加拿大
仅开盒Instax mini 9。几乎全新。价格70。 有意请联系5146239982




0 && lastRunQueue[0]  timeLimitation) {            console.error(&quot;limited&quot;)            return true;        }        lastRunQueue.push(Date.now() / 1000);        return false;    }    function skipIntroLen() {        try {            let len = parseInt(window.VideoTogetherStorage.SkipIntroLength);            if (window.VideoTogetherStorage.SkipIntro && !isNaN(len)) {                return len;            }        } catch { }        return 0;    }    function isEmpty(s) {        try {            return s.length == 0;        } catch {            return true;        }    }    function emptyStrIfUdf(s) {        return s == undefined ? &quot;&quot; : s;    }    function isEasyShareEnabled() {        try {            if (isWeb()) {                return false;            }            const hostname = window.location.hostname;            if (hostname.endsWith(&quot;iqiyi.com&quot;) || hostname.endsWith(&quot;qq.com&quot;) || hostname.endsWith(&quot;youku.com&quot;)) {                return false;            }            return window.VideoTogetherEasyShare != 'disabled' && window.VideoTogetherStorage.EasyShare != false;        } catch {            return false;        }    }    function isEasyShareMember() {        try {            return window.VideoTogetherEasyShareMemberSite == true;        } catch {            return false;        }    }    const mediaUrlsCache = {}    function extractMediaUrls(m3u8Content, m3u8Url) {        if (mediaUrlsCache[m3u8Url] == undefined) {            let lines = m3u8Content.split(&quot;\n&quot;);            let mediaUrls = [];            let base = new URL(m3u8Url);            for (let i = 0; i  `%${c.charCodeAt(0).toString(16).toUpperCase()}`        ).replace(/%20/g, '+');    }    function fixedDecodeURIComponent(str) {        return decodeURIComponent(str.replace(/\+/g, ' '));    }    function isWeb(type) {        return type == 'website' || type == 'website_debug';    }    /**     * @returns {Element}     */    function select(query) {        let e = window.videoTogetherFlyPannel.wrapper.querySelector(query);        return e;    }    function hide(e) {        if (e) e.style.display = 'none';    }    function show(e) {        if (e) e.style.display = null;    }    function isVideoLoadded(video) {        try {            if (isNaN(video.readyState)) {                return true;            }            return video.readyState >= 3;        } catch {            return true;        }    }    function isRoomProtected() {        try {            return window.VideoTogetherStorage == undefined || window.VideoTogetherStorage.PasswordProtectedRoom != false;        } catch {            return true;        }    }    function changeBackground(url) {        let e = select('.vt-modal-body');        if (e) {            if (url == null || url == &quot;&quot;) {                e.style.backgroundImage = 'none';            } else if (e.style.backgroundImage != `url(&quot;${url}&quot;)`) {                e.style.backgroundImage = `url(&quot;${url}&quot;)`            }        }    }    function changeMemberCount(c) {        select('#memberCount').innerHTML = String.fromCodePoint(&quot;0x1f465&quot;) + &quot; &quot; + c    }    function dsply(e, _show = true) {        _show ? show(e) : hide(e);    }    async function isAudioVolumeRO() {        let a = new Audio();        a.volume = 0.5;        return new Promise(r => setTimeout(() => {            r(!(a.volume == 0.5))        }, 1));    }    const Global = {        inited: false,        NativePostMessageFunction: null,        NativeAttachShadow: null,        NativeFetch: null    }    function AttachShadow(e, options) {        try {            return e.attachShadow(options);        } catch (err) {            GetNativeFunction();            return Global.NativeAttachShadow.call(e, options);        }    }    function GetNativeFunction() {        if (Global.inited) {            return;        }        Global.inited = true;        let temp = document.createElement(&quot;iframe&quot;);        hide(temp);        document.body.append(temp);        Global.NativePostMessageFunction = temp.contentWindow.postMessage;        Global.NativeAttachShadow = temp.contentWindow.Element.prototype.attachShadow;        Global.NativeFetch = temp.contentWindow.fetch;    }    function PostMessage(window, data) {        if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.postMessage))) {            window.postMessage(data, &quot;*&quot;);        } else {            GetNativeFunction();            Global.NativePostMessageFunction.call(window, data, &quot;*&quot;);        }    }    function sendMessageToTop(type, data) {        PostMessage(window.top, {            source: &quot;VideoTogether&quot;,            type: type,            data: data        });    }    function sendMessageToSelf(type, data) {        PostMessage(window, {            source: &quot;VideoTogether&quot;,            type: type,            data: data        });    }    function sendMessageTo(w, type, data) {        PostMessage(w, {            source: &quot;VideoTogether&quot;,            type: type,            data: data        });    }    function initRangeSlider(slider) {        const min = slider.min        const max = slider.max        const value = slider.value        slider.style.background = `linear-gradient(to right, #1abc9c 0%, #1abc9c ${(value - min) / (max - min) * 100}%, #d7dcdf ${(value - min) / (max - min) * 100}%, #d7dcdf 100%)`        slider.addEventListener('input', function () {            this.style.background = `linear-gradient(to right, #1abc9c 0%, #1abc9c ${(this.value - this.min) / (this.max - this.min) * 100}%, #d7dcdf ${(this.value - this.min) / (this.max - this.min) * 100}%, #d7dcdf 100%)`        });    }    function WSUpdateRoomRequest(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url) {        return {            &quot;method&quot;: &quot;/room/update&quot;,            &quot;data&quot;: {                &quot;tempUser&quot;: extension.tempUser,                &quot;password&quot;: password,                &quot;name&quot;: name,                &quot;playbackRate&quot;: playbackRate,                &quot;currentTime&quot;: currentTime,                &quot;paused&quot;: paused,                &quot;url&quot;: url,                &quot;lastUpdateClientTime&quot;: localTimestamp,                &quot;duration&quot;: duration,                &quot;protected&quot;: isRoomProtected(),                &quot;videoTitle&quot;: extension.isMain ? document.title : extension.videoTitle,                &quot;sendLocalTimestamp&quot;: Date.now() / 1000,                &quot;m3u8Url&quot;: m3u8Url            }        }    }    function WSJoinRoomRequest(name, password) {        return {            &quot;method&quot;: &quot;/room/join&quot;,            &quot;data&quot;: {                &quot;password&quot;: password,                &quot;name&quot;: name,            }        }    }    function WsUpdateMemberRequest(name, password, isLoadding, currentUrl) {        return {            &quot;method&quot;: &quot;/room/update_member&quot;,            &quot;data&quot;: {                &quot;password&quot;: password,                &quot;roomName&quot;: name,                &quot;sendLocalTimestamp&quot;: Date.now() / 1000,                &quot;userId&quot;: extension.tempUser,                &quot;isLoadding&quot;: isLoadding,                &quot;currentUrl&quot;: currentUrl            }        }    }    function popupError(msg) {        let x = select(&quot;#snackbar&quot;);        x.innerHTML = msg;        x.className = &quot;show&quot;;        setTimeout(function () { x.className = x.className.replace(&quot;show&quot;, &quot;&quot;); }, 3000);    }    async function waitForRoomUuid(timeout = 10000) {        return new Promise((res, rej) => {            let id = setInterval(() => {                if (roomUuid != null) {                    res(roomUuid);                    clearInterval(id);                }            }, 200)            setTimeout(() => {                clearInterval(id);                rej(null);            }, timeout);        });    }    class Room {        constructor() {            this.currentTime = null;            this.duration = null;            this.lastUpdateClientTime = null;            this.lastUpdateServerTime = null;            this.name = null;            this.paused = null;            this.playbackRate = null;            this.protected = null;            this.timestamp = null;            this.url = null;            this.videoTitle = null;        }    }    const WS = {        _socket: null,        _lastConnectTime: 0,        _connectTimeout: 10,        _expriedTime: 5,        _lastUpdateTime: 0,        _lastErrorMessage: null,        _lastRoom: new Room(),        async connect() {            if (this._socket != null) {                try {                    if (this._socket.readyState == 1) {                        return;                    }                    if (this._socket.readyState == 0                        && this._lastConnectTime + this._connectTimeout > Date.now() / 1000) {                        return;                    }                } catch { }            }            console.log('ws connect');            this._lastConnectTime = Date.now() / 1000            try {                this.disconnect()                this._socket = new WebSocket(`wss://vt.panghair.com:5000/ws?language=${language}`);                this._socket.onmessage = async e => {                    let lines = e.data.split('\n');                    for (let i = 0; i  Date.now() / 1000) {                if (this._lastErrorMessage != null) {                    throw new Error(this._lastErrorMessage);                }                return this._lastRoom;            }        },        async send(data) {            try {                this._socket.send(JSON.stringify(data));            } catch { }        },        async updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url) {            // TODO localtimestamp            this.send(WSUpdateRoomRequest(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url));        },        async urlReq(m3u8Url, idx, origin) {            this.send({                &quot;method&quot;: &quot;url_req&quot;,                &quot;data&quot;: {                    &quot;m3u8Url&quot;: m3u8Url,                    &quot;idx&quot;: idx,                    &quot;origin&quot;: origin                }            })        },        async urlResp(origin, real) {            this.send({                &quot;method&quot;: &quot;url_resp&quot;,                &quot;data&quot;: {                    &quot;origin&quot;: origin,                    &quot;real&quot;: real,                }            })        },        async updateMember(name, password, isLoadding, currentUrl) {            this.send(WsUpdateMemberRequest(name, password, isLoadding, currentUrl));        },        _joinedName: null,        async joinRoom(name, password) {            if (name == this._joinedName) {                return;            }            this.send(WSJoinRoomRequest(name, password));        },        async disconnect() {            if (this._socket != null) {                try {                    this._socket.close();                } catch { }            }            this._joinedName = null;            this._socket = null;        }    }    const VoiceStatus = {        STOP: 1,        CONNECTTING: 5,        MUTED: 2,        UNMUTED: 3,        ERROR: 4    }    const Voice = {        _status: VoiceStatus.STOP,        _errorMessage: &quot;&quot;,        _rname: &quot;&quot;,        _mutting: false,        get errorMessage() {            return this._errorMessage;        },        set errorMessage(m) {            this._errorMessage = m;            select(&quot;#snackbar&quot;).innerHTML = m;            let voiceConnErrBtn = select('#voiceConnErrBtn');            if (voiceConnErrBtn != undefined) {                voiceConnErrBtn.onclick = () => {                    alert('如果你安装了uBlock等去广告插件,请停用这些去广告插件后再试')                }            }        },        set status(s) {            this._status = s;            let disabledMic = select(&quot;#disabledMic&quot;);            let micBtn = select('#micBtn');            let audioBtn = select('#audioBtn');            let callBtn = select(&quot;#callBtn&quot;);            let callConnecting = select(&quot;#callConnecting&quot;);            let callErrorBtn = select(&quot;#callErrorBtn&quot;);            dsply(callConnecting, s == VoiceStatus.CONNECTTING);            dsply(callBtn, s == VoiceStatus.STOP);            let inCall = (VoiceStatus.UNMUTED == s || VoiceStatus.MUTED == s);            dsply(micBtn, inCall);            dsply(audioBtn, inCall);            dsply(callErrorBtn, s == VoiceStatus.ERROR);            switch (s) {                case VoiceStatus.STOP:                    break;                case VoiceStatus.MUTED:                    show(disabledMic);                    break;                case VoiceStatus.UNMUTED:                    hide(disabledMic);                    break;                case VoiceStatus.ERROR:                    var x = select(&quot;#snackbar&quot;);                    x.className = &quot;show&quot;;                    setTimeout(function () { x.className = x.className.replace(&quot;show&quot;, &quot;&quot;); }, 3000);                    break;                default:                    break;            }        },        get status() {            return this._status;        },        _conn: null,        set conn(conn) {            this._conn = conn;        },        /**         * @return {RTCPeerConnection}         */        get conn() {            return this._conn        },        _stream: null,        set stream(s) {            this._stream = s;        },        /**         * @return {MediaStream}         */        get stream() {            return this._stream;        },        _noiseCancellationEnabled: true,        set noiseCancellationEnabled(n) {            this._noiseCancellationEnabled = n;            if (this.inCall) {                this.updateVoiceSetting(n);            }        },        get noiseCancellationEnabled() {            return this._noiseCancellationEnabled;        },        get inCall() {            return this.status == VoiceStatus.MUTED || this.status == VoiceStatus.UNMUTED;        },        join: async function (name, rname, mutting = false) {            Voice._rname = rname;            Voice._mutting = mutting;            let cancellingNoise = true;            try {                cancellingNoise = !(window.VideoTogetherStorage.EchoCancellation === false);            } catch { }            Voice.stop();            Voice.status = VoiceStatus.CONNECTTING;            this.noiseCancellationEnabled = cancellingNoise;            let uid = generateUUID();            let notNullUuid;            try {                notNullUuid = await waitForRoomUuid();            } catch {                Voice.errorMessage = &quot;uuid缺失&quot;;                Voice.status = VoiceStatus.ERROR;                return;            }            const rnameRPC = fixedEncodeURIComponent(notNullUuid + &quot;_&quot; + rname);            if (rnameRPC.length > 256) {                Voice.errorMessage = &quot;房间名太长&quot;;                Voice.status = VoiceStatus.ERROR;                return;            }            if (window.location.protocol != &quot;https:&quot;) {                Voice.errorMessage = &quot;仅支持https网站使用&quot;;                Voice.status = VoiceStatus.ERROR;                return;            }            const unameRPC = fixedEncodeURIComponent(uid + ':' + Base64.encode(generateUUID()));            let ucid = &quot;&quot;;            console.log(rnameRPC, uid);            const configuration = {                bundlePolicy: 'max-bundle',                rtcpMuxPolicy: 'require',                sdpSemantics: 'unified-plan'            };            async function subscribe(pc) {                var res = await rpc('subscribe', [rnameRPC, unameRPC, ucid]);                if (res.error && typeof res.error === 'object' && typeof res.error.code === 'number' && [5002001, 5002002].indexOf(res.error.code) != -1) {                    Voice.join(&quot;&quot;, Voice._rname, Voice._mutting);                    return;                }                if (res.data) {                    var jsep = JSON.parse(res.data.jsep);                    if (jsep.type == 'offer') {                        await pc.setRemoteDescription(jsep);                        var sdp = await pc.createAnswer();                        await pc.setLocalDescription(sdp);                        await rpc('answer', [rnameRPC, unameRPC, ucid, JSON.stringify(sdp)]);                    }                }                setTimeout(function () {                    if (Voice.conn != null && pc === Voice.conn && Voice.status != VoiceStatus.STOP) {                        subscribe(pc);                    }                }, 3000);            }            try {                await start();            } catch (e) {                if (Voice.status == VoiceStatus.CONNECTTING) {                    Voice.status = VoiceStatus.ERROR;                    Voice.errorMessage = &quot;连接失败 (帮助)&quot;;                }            }            if (Voice.status == VoiceStatus.CONNECTTING) {                Voice.status = mutting ? VoiceStatus.MUTED : VoiceStatus.UNMUTED;            }            async function start() {                let res = await rpc('turn', [unameRPC]);                if (res.data && res.data.length > 0) {                    configuration.iceServers = res.data;                    configuration.iceTransportPolicy = 'relay';                }                Voice.conn = new RTCPeerConnection(configuration);                Voice.conn.onicecandidate = ({ candidate }) => {                    rpc('trickle', [rnameRPC, unameRPC, ucid, JSON.stringify(candidate)]);                };                Voice.conn.ontrack = (event) => {                    console.log(&quot;ontrack&quot;, event);                    let stream = event.streams[0];                    let sid = fixedDecodeURIComponent(stream.id);                    let id = sid.split(':')[0];                    // var name = Base64.decode(sid.split(':')[1]);                    console.log(id, uid);                    if (id === uid) {                        return;                    }                    event.track.onmute = (event) => {                        console.log(&quot;onmute&quot;, event);                    };                    let aid = 'peer-audio-' + id;                    let el = select('#' + aid);                    if (el) {                        el.srcObject = stream;                    } else {                        el = document.createElement(event.track.kind)                        el.id = aid;                        el.srcObject = stream;                        el.autoplay = true;                        el.controls = false;                        select('#peer').appendChild(el);                    }                };                try {                    const constraints = {                        audio: {                            echoCancellation: cancellingNoise,                            noiseSuppression: cancellingNoise                        },                        video: false                    };                    Voice.stream = await navigator.mediaDevices.getUserMedia(constraints);                } catch (err) {                    if (Voice.status == VoiceStatus.CONNECTTING) {                        Voice.errorMessage = &quot;麦克风权限获取失败&quot;;                        Voice.status = VoiceStatus.ERROR;                    }                    return;                }                Voice.stream.getTracks().forEach((track) => {                    track.enabled = !mutting;                    Voice.conn.addTrack(track, Voice.stream);                });                await Voice.conn.setLocalDescription(await Voice.conn.createOffer());                res = await rpc('publish', [rnameRPC, unameRPC, JSON.stringify(Voice.conn.localDescription)]);                if (res.data) {                    let jsep = JSON.parse(res.data.jsep);                    if (jsep.type == 'answer') {                        await Voice.conn.setRemoteDescription(jsep);                        ucid = res.data.track;                        await subscribe(Voice.conn);                    }                } else {                    throw new Error('未知错误');                }                Voice.conn.oniceconnectionstatechange = e => {                    if (Voice.conn.iceConnectionState == &quot;disconnected&quot; || Voice.conn.iceConnectionState == &quot;failed&quot; || Voice.conn.iceConnectionState == &quot;closed&quot;) {                        Voice.errorMessage = &quot;连接断开&quot;;                        Voice.status = VoiceStatus.ERROR;                    } else {                        if (Voice.status == VoiceStatus.ERROR) {                            Voice.status = Voice._mutting ? VoiceStatus.MUTED : VoiceStatus.UNMUTED;                        }                    }                }            }            async function rpc(method, params = [], retryTime = -1) {                try {                    const response = await window.videoTogetherExtension.Fetch(extension.video_together_host + &quot;/kraken&quot;, &quot;POST&quot;, { id: generateUUID(), method: method, params: params }, {                        method: 'POST', // *GET, POST, PUT, DELETE, etc.                        mode: 'cors', // no-cors, *cors, same-origin                        cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached                        credentials: 'omit', // include, *same-origin, omit                        headers: {                            'Content-Type': 'application/json'                        },                        redirect: 'follow', // manual, *follow, error                        referrerPolicy: 'no-referrer', // no-referrer, *client                        body: JSON.stringify({ id: generateUUID(), method: method, params: params }) // body data type must match &quot;Content-Type&quot; header                    });                    return await response.json(); // parses JSON response into native JavaScript objects                } catch (err) {                    if (Voice.status == VoiceStatus.STOP) {                        return;                    }                    if (retryTime == 0) {                        throw err;                    }                    await new Promise(r => setTimeout(r, 1000));                    return await rpc(method, params, retryTime - 1);                }            }        },        stop: () => {            try {                Voice.conn.getSenders().forEach(s => {                    if (s.track) {                        s.track.stop();                    }                });            } catch (e) { };            [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => e.remove());            try {                Voice.conn.close();                delete Voice.conn;            } catch { }            try {                Voice.stream.getTracks().forEach(function (track) {                    track.stop();                });                delete Voice.stream;            } catch { }            Voice.status = VoiceStatus.STOP;        },        mute: () => {            Voice.conn.getSenders().forEach(s => {                if (s.track) {                    s.track.enabled = false;                }            });            Voice._mutting = true;            Voice.status = VoiceStatus.MUTED;        },        unmute: () => {            Voice.conn.getSenders().forEach(s => {                if (s.track) {                    s.track.enabled = true;                }            });            Voice._mutting = false;            Voice.status = VoiceStatus.UNMUTED;        },        updateVoiceSetting: async (cancellingNoise = false) => {            const constraints = {                audio: {                    echoCancellation: cancellingNoise,                    noiseSuppression: cancellingNoise                },                video: false            };            try {                prevStream = Voice.stream;                Voice.stream = await navigator.mediaDevices.getUserMedia(constraints);                Voice.conn.getSenders().forEach(s => {                    if (s.track) {                        s.replaceTrack(Voice.stream.getTracks().find(t => t.kind == s.track.kind));                    }                })                prevStream.getTracks().forEach(t => t.stop());                delete prevStream;            } catch (e) { console.log(e); };        }    }    function generateUUID() {        if (crypto.randomUUID != undefined) {            return crypto.randomUUID();        }        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)        );    }    function generateTempUserId() {        return generateUUID() + &quot;:&quot; + Date.now() / 1000;    }    /**     *     *  Base64 encode / decode     *  http://www.webtoolkit.info     *     **/    const Base64 = {        // private property        _keyStr: &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&quot;        // public method for encoding        , encode: function (input) {            var output = &quot;&quot;;            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;            var i = 0;            input = Base64._utf8_encode(input);            while (i > 2;                enc2 = ((chr1 & 3) <> 4);                enc3 = ((chr2 & 15) <> 6);                enc4 = chr3 & 63;                if (isNaN(chr2)) {                    enc3 = enc4 = 64;                }                else if (isNaN(chr3)) {                    enc4 = 64;                }                output = output +                    this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +                    this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);            } // Whend            return output;        } // End Function encode        // public method for decoding        , decode: function (input) {            var output = &quot;&quot;;            var chr1, chr2, chr3;            var enc1, enc2, enc3, enc4;            var i = 0;            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, &quot;&quot;);            while (i < input.length) {                enc1 = this._keyStr.indexOf(input.charAt(i++));                enc2 = this._keyStr.indexOf(input.charAt(i++));                enc3 = this._keyStr.indexOf(input.charAt(i++));                enc4 = this._keyStr.indexOf(input.charAt(i++));                chr1 = (enc1 <> 4);                chr2 = ((enc2 & 15) <> 2);                chr3 = ((enc3 & 3) << 6) | enc4;                output = output + String.fromCharCode(chr1);                if (enc3 != 64) {                    output = output + String.fromCharCode(chr2);                }                if (enc4 != 64) {                    output = output + String.fromCharCode(chr3);                }            } // Whend            output = Base64._utf8_decode(output);            return output;        } // End Function decode        // private method for UTF-8 encoding        , _utf8_encode: function (string) {            var utftext = &quot;&quot;;            string = string.replace(/\r\n/g, &quot;\n&quot;);            for (var n = 0; n < string.length; n++) {                var c = string.charCodeAt(n);                if (c  127) && (c > 6) | 192);                    utftext += String.fromCharCode((c & 63) | 128);                }                else {                    utftext += String.fromCharCode((c >> 12) | 224);                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);                    utftext += String.fromCharCode((c & 63) | 128);                }            } // Next n            return utftext;        } // End Function _utf8_encode        // private method for UTF-8 decoding        , _utf8_decode: function (utftext) {            var string = &quot;&quot;;            var i = 0;            var c, c1, c2, c3;            c = c1 = c2 = 0;            while (i < utftext.length) {                c = utftext.charCodeAt(i);                if (c  191) && (c < 224)) {                    c2 = utftext.charCodeAt(i + 1);                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));                    i += 2;                }                else {                    c2 = utftext.charCodeAt(i + 1);                    c3 = utftext.charCodeAt(i + 2);                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));                    i += 3;                }            } // Whend            return string;        } // End Function _utf8_decode    }    class VideoTogetherFlyPannel {        constructor() {            this.sessionKey = &quot;VideoTogetherFlySaveSessionKey&quot;;            this.isInRoom = false;            this.isMain = (window.self == window.top);            if (this.isMain) {                this.minimized = false;                let shadowWrapper = document.createElement(&quot;div&quot;);                shadowWrapper.id = &quot;VideoTogetherWrapper&quot;;                let wrapper;                try {                    wrapper = AttachShadow(shadowWrapper, { mode: &quot;open&quot; });                } catch (e) { console.error(e); }                this.shadowWrapper = shadowWrapper;                this.wrapper = wrapper;                wrapper.innerHTML = `
            [img=16,16][/img]      VideoTogether
   
                                                                                                                                                                                                              
                                                
        
                  房间                  
                  密码                  
                              复制轻松分享链接                  
      
              
          视频音量                                
        

        
          通话音量                                
        

                  IOS不支持音量调节
        
              
   
   
                            建 房                          加 入              
                        退 出                          通 话                          
         
         
         
        
                                                                                                                                                                                                         
                                                                                                                                                                                                                                            帮 助         
  

  [img][/img]  
`;                (document.body || document.documentElement).appendChild(shadowWrapper);                wrapper.querySelector(&quot;#videoTogetherMinimize&quot;).onclick = () => { this.Minimize() }                wrapper.querySelector(&quot;#videoTogetherMaximize&quot;).onclick = () => { this.Maximize() }                [&quot;&quot;, &quot;webkit&quot;].forEach(prefix => {                    document.addEventListener(prefix + &quot;fullscreenchange&quot;, (event) => {                        if (document.fullscreenElement || document.webkitFullscreenElement) {                            hide(this.videoTogetherFlyPannel);                            hide(this.videoTogetherSamllIcon);                        } else {                            if (this.minimized) {                                this.Minimize();                            } else {                                this.Maximize();                            }                        }                    });                });                this.lobbyBtnGroup = wrapper.querySelector(&quot;#lobbyBtnGroup&quot;);                this.createRoomButton = wrapper.querySelector('#videoTogetherCreateButton');                this.joinRoomButton = wrapper.querySelector(&quot;#videoTogetherJoinButton&quot;);                this.roomButtonGroup = wrapper.querySelector('#roomButtonGroup');                this.exitButton = wrapper.querySelector(&quot;#videoTogetherExitButton&quot;);                this.callBtn = wrapper.querySelector(&quot;#callBtn&quot;);                this.callBtn.onclick = () => Voice.join(&quot;&quot;, window.videoTogetherExtension.roomName);                this.helpButton = wrapper.querySelector(&quot;#videoTogetherHelpButton&quot;);                this.audioBtn = wrapper.querySelector(&quot;#audioBtn&quot;);                this.micBtn = wrapper.querySelector(&quot;#micBtn&quot;);                this.videoVolume = wrapper.querySelector(&quot;#videoVolume&quot;);                this.callVolumeSlider = wrapper.querySelector(&quot;#callVolume&quot;);                this.callErrorBtn = wrapper.querySelector(&quot;#callErrorBtn&quot;);                this.easyShareCopyBtn = wrapper.querySelector(&quot;#easyShareCopyBtn&quot;);                this.easyShareCopyBtn.onclick = async () => {                    try {                        await navigator.clipboard.writeText(&quot;点击链接,和我一起看吧: , 如果打不开可以尝试备用链接:&quot;                            .replace(&quot;&quot;, extension.generateEasyShareLink())                            .replace(&quot;&quot;, extension.generateEasyShareLink(true)));                        popupError(&quot;复制成功,快去分享吧&quot;);                    } catch {                        popupError(&quot;复制失败&quot;);                    }                }                this.callErrorBtn.onclick = () => {                    Voice.join(&quot;&quot;, window.videoTogetherExtension.roomName);                }                this.videoVolume.oninput = () => {                    extension.videoVolume = this.videoVolume.value;                    sendMessageToTop(MessageType.ChangeVideoVolume, { volume: extension.getVideoVolume() / 100 });                }                this.callVolumeSlider.oninput = () => {                    extension.voiceVolume = this.callVolumeSlider.value;                    [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => {                        e.volume = extension.getVoiceVolume() / 100;                    });                }                initRangeSlider(this.videoVolume);                initRangeSlider(this.callVolumeSlider);                this.audioBtn.onclick = async () => {                    let hideMain = select('#mainPannel').style.display == 'none';                    dsply(select('#mainPannel'), hideMain);                    dsply(select('#voicePannel'), !hideMain);                    if (!hideMain) {                        this.audioBtn.style.color = '#1890ff';                    } else {                        this.audioBtn.style.color = '#6c6c6c';                    }                    if (await isAudioVolumeRO()) {                        show(select('#iosVolumeErr'));                        hide(select('#videoVolumeCtrl'));                        hide(select('#callVolumeCtrl'));                    }                }                this.micBtn.onclick = async () => {                    switch (Voice.status) {                        case VoiceStatus.STOP: {                            // TODO need fix                            await Voice.join();                            break;                        }                        case VoiceStatus.UNMUTED: {                            Voice.mute();                            break;                        }                        case VoiceStatus.MUTED: {                            Voice.unmute();                            break;                        }                    }                }                this.createRoomButton.onclick = this.CreateRoomButtonOnClick.bind(this);                this.joinRoomButton.onclick = this.JoinRoomButtonOnClick.bind(this);                this.helpButton.onclick = this.HelpButtonOnClick.bind(this);                this.exitButton.onclick = (() => {                    window.videoTogetherExtension.exitRoom();                });                this.videoTogetherRoleText = wrapper.querySelector(&quot;#videoTogetherRoleText&quot;)                this.videoTogetherSetting = wrapper.querySelector(&quot;#videoTogetherSetting&quot;);                hide(this.videoTogetherSetting);                this.inputRoomName = wrapper.querySelector('#videoTogetherRoomNameInput');                this.inputRoomPassword = wrapper.querySelector(&quot;#videoTogetherRoomPasswordInput&quot;);                this.inputRoomNameLabel = wrapper.querySelector('#videoTogetherRoomNameLabel');                this.inputRoomPasswordLabel = wrapper.querySelector(&quot;#videoTogetherRoomPasswordLabel&quot;);                this.videoTogetherHeader = wrapper.querySelector(&quot;#videoTogetherHeader&quot;);                this.videoTogetherFlyPannel = wrapper.getElementById(&quot;videoTogetherFlyPannel&quot;);                this.videoTogetherSamllIcon = wrapper.getElementById(&quot;videoTogetherSamllIcon&quot;);                this.volume = 1;                this.statusText = wrapper.querySelector(&quot;#videoTogetherStatusText&quot;);                this.InLobby(true);                this.Init();                setInterval(() => {                    this.ShowPannel();                }, 1000);            }            try {                document.querySelector(&quot;#videoTogetherLoading&quot;).remove()            } catch { }        }        ShowPannel() {            if (!document.documentElement.contains(this.shadowWrapper)) {                (document.body || document.documentElement).appendChild(this.shadowWrapper);            }        }        Minimize(isDefault = false) {            this.minimized = true;            if (!isDefault) {                this.SaveIsMinimized(true);            }            this.disableDefaultSize = true;            hide(this.videoTogetherFlyPannel);            show(this.videoTogetherSamllIcon);        }        Maximize(isDefault = false) {            this.minimized = false;            if (!isDefault) {                this.SaveIsMinimized(false);            }            this.disableDefaultSize = true;            show(this.videoTogetherFlyPannel);            hide(this.videoTogetherSamllIcon);        }        SaveIsMinimized(minimized) {            localStorage.setItem(&quot;VideoTogetherMinimizedHere&quot;, minimized ? 1 : 0)        }        Init() {            let VideoTogetherMinimizedHere = localStorage.getItem(&quot;VideoTogetherMinimizedHere&quot;);            if (VideoTogetherMinimizedHere == 0) {                this.Maximize(true);            } else if (VideoTogetherMinimizedHere == 1) {                this.Minimize(true);            }        }        InRoom() {            this.Maximize();            this.inputRoomName.disabled = true;            hide(this.lobbyBtnGroup)            show(this.roomButtonGroup);            this.exitButton.style = &quot;&quot;;            hide(this.inputRoomPasswordLabel);            hide(this.inputRoomPassword);            this.inputRoomName.placeholder = &quot;&quot;;            this.isInRoom = true;        }        InLobby(init = false) {            if (!init) {                this.Maximize();            }            this.inputRoomName.disabled = false;            this.inputRoomPasswordLabel.style.display = &quot;inline-block&quot;;            this.inputRoomPassword.style.display = &quot;inline-block&quot;;            this.inputRoomName.placeholder = &quot;请输入房间名&quot;            show(this.lobbyBtnGroup);            hide(this.roomButtonGroup);            hide(this.easyShareCopyBtn);            this.isInRoom = false;        }        CreateRoomButtonOnClick() {            this.Maximize();            let roomName = this.inputRoomName.value;            let password = this.inputRoomPassword.value;            window.videoTogetherExtension.CreateRoom(roomName, password);        }        JoinRoomButtonOnClick() {            this.Maximize();            let roomName = this.inputRoomName.value;            let password = this.inputRoomPassword.value;            window.videoTogetherExtension.JoinRoom(roomName, password);        }        HelpButtonOnClick() {            this.Maximize();            let url = 'https://2gether.video/guide/qa.html';            if (vtRuntime == &quot;website&quot;) {                url = &quot;https://2gether.video/guide/website_qa.html&quot;            }            window.open(url, '_blank');        }        UpdateStatusText(text, color) {            this.statusText.innerHTML = text;            this.statusText.style.color = color;        }    }    class VideoModel {        constructor(id, duration, activatedTime, refreshTime, priority = 0) {            this.id = id;            this.duration = duration;            this.activatedTime = activatedTime;            this.refreshTime = refreshTime;            this.priority = priority;        }    }    let MessageType = {        ActivatedVideo: 1,        ReportVideo: 2,        SyncMemberVideo: 3,        SyncMasterVideo: 4,        UpdateStatusText: 5,        JumpToNewPage: 6,        GetRoomData: 7,        ChangeVoiceVolume: 8,        ChangeVideoVolume: 9,        FetchRequest: 13,        FetchResponse: 14,        SetStorageValue: 15,        SyncStorageValue: 16,        ExtensionInitSuccess: 17,        SetTabStorage: 18,        SetTabStorageSuccess: 19,        UpdateRoomRequest: 20,        CallScheduledTask: 21,        RoomDataNotification: 22,        UpdateMemberStatus: 23,        TimestampV2Resp: 24,        EasyShareCheckSucc: 25,        FetchRealUrlReq: 26,        FetchRealUrlResp: 27,        FetchRealUrlFromIframeReq: 28,        FetchRealUrlFromIframeResp: 29,        UpdateM3u8Files: 1001,    }    let VIDEO_EXPIRED_SECOND = 10    class VideoWrapper {        set currentTime(v) {            this.currentTimeSetter(v);        }        get currentTime() {            return this.currentTimeGetter();        }        set playbackRate(v) {            this.playbackRateSetter(v);        }        get playbackRate() {            return this.playbackRateGetter();        }        constructor(play, pause, paused, currentTimeGetter, currentTimeSetter, duration, playbackRateGetter, playbackRateSetter) {            this.play = play;            this.pause = pause;            this.paused = paused;            this.currentTimeGetter = currentTimeGetter;            this.currentTimeSetter = currentTimeSetter;            this.duration = duration;            this.playbackRateGetter = playbackRateGetter;            this.playbackRateSetter = playbackRateSetter;        }    }    class VideoTogetherExtension {        constructor() {            this.RoleEnum = {                Null: 1,                Master: 2,                Member: 3,            }            this.cspBlockedHost = {};            this.video_together_host = 'https://vt.panghair.com:5000/';            this.video_together_backup_host = 'https://api.chizhou.in/';            this.video_tag_names = [&quot;video&quot;, &quot;bwp-video&quot;]            this.timer = 0            this.roomName = &quot;&quot;            this.roomPassword = &quot;&quot;            this.role = this.RoleEnum.Null            this.url = &quot;&quot;            this.duration = undefined            this.waitForLoadding = false;            this.playAfterLoadding = false;            this.minTrip = 1e9;            this.timeOffset = 0;            this.lastScheduledTaskTs = 0;            this.httpSucc = false;            this.activatedVideo = undefined;            this.tempUser = generateTempUserId();            this.version = '1685887469';            this.isMain = (window.self == window.top);            this.UserId = undefined;            this.callbackMap = new Map;            this.allLinksTargetModified = false;            this.voiceVolume = null;            this.videoVolume = null;            this.m3u8Files = {};            this.m3u8PostWindows = {};            this.m3u8MediaUrls = {};            // blockedFiles won't be set to false, if allowed            this.blockedM3u8Files = {};            this.allowedM3u8Files = {};            this.currentM3u8Url = undefined;            // we need a common callback function to deal with all message            this.SetTabStorageSuccessCallback = () => { };            document.addEventListener(&quot;securitypolicyviolation&quot;, (e) => {                let host = (new URL(e.blockedURI)).host;                this.cspBlockedHost[host] = true;            });            try {                this.CreateVideoDomObserver();            } catch { }            this.timer = setInterval(() => this.ScheduledTask(true), 2 * 1000);            this.videoMap = new Map();            window.addEventListener('message', message => {                if (message.data.context) {                    this.tempUser = message.data.context.tempUser;                    this.videoTitle = message.data.context.videoTitle;                    this.voiceStatus = message.data.context.voiceStatus;                    this.timeOffset = message.data.context.timeOffset;                    // sub frame has 2 storage data source, top frame or extension.js in this frame                    // this 2 data source should be same.                    window.VideoTogetherStorage = message.data.context.VideoTogetherStorage;                }                this.processReceivedMessage(message.data.type, message.data.data, message);            });            // if some element's click be invoked frequenctly, a lot of http request will be sent            // window.addEventListener('click', message => {            //     setTimeout(this.ScheduledTask.bind(this), 200);            // })            if (this.isMain) {                try {                    try {                        this.RecoveryState();                    } catch { }                    this.EnableDraggable();                    setTimeout(() => {                        let allDoms = document.querySelectorAll(&quot;*&quot;);                        for (let i = 0; i  {                window.videoTogetherFlyPannel.videoTogetherRoleText.innerHTML = text;            }            this.role = role            switch (role) {                case this.RoleEnum.Master:                    setRoleText(&quot;房主&quot;);                    break;                case this.RoleEnum.Member:                    setRoleText(&quot;成员&quot;);                    break;                default:                    setRoleText(&quot;&quot;);                    break;            }        }        generateEasyShareLink(china = false) {            if (china) {                return `https://videotogether.gitee.io/${language}/easyshare.html?VideoTogetherRole=3&VideoTogetherRoomName=${this.roomName}&VideoTogetherTimestamp=9999999999&VideoTogetherUrl=&VideoTogetherPassword=${this.password}`            } else {                return `https://2gether.video/${language}/easyshare.html?VideoTogetherRole=3&VideoTogetherRoomName=${this.roomName}&VideoTogetherTimestamp=9999999999&VideoTogetherUrl=&VideoTogetherPassword=${this.password}`;            }        }        async Fetch(url, method = 'GET', data = null) {            if (!extension.isMain) {                console.error(&quot;fetch in child&quot;);                throw new Error(&quot;fetch in child&quot;);            }            url = new URL(url);            url.searchParams.set(&quot;version&quot;, this.version);            try {                url.searchParams.set(&quot;language&quot;, language);                url.searchParams.set(&quot;voiceStatus&quot;, this.isMain ? Voice.status : this.voiceStatus);                url.searchParams.set(&quot;loaddingVersion&quot;, window.VideoTogetherStorage.LoaddingVersion);                url.searchParams.set(&quot;runtimeType&quot;, window.VideoTogetherStorage.UserscriptType);            } catch (e) { }            try {                url.searchParams.set(&quot;userId&quot;, window.VideoTogetherStorage.PublicUserId);            } catch (e) { }            url = url.toString();            let host = (new URL(url)).host;            if (this.cspBlockedHost[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: &quot;超时&quot; });                            }                        } finally {                            this.callbackMap.delete(id);                        }                    }, 20000);                });            }            if (/\{\s+\[native code\]/.test(Function.prototype.toString.call(window.fetch))) {                const controller = new AbortController();                const timeoutId = setTimeout(() => controller.abort(), 10000);                return await window.fetch(url, {                    method: method,                    body: data == null ? undefined : JSON.stringify(data),                    signal: controller.signal                });            } else {                GetNativeFunction();                const controller = new AbortController();                const timeoutId = setTimeout(() => controller.abort(), 10000);                return await Global.NativeFetch.call(window, url, {                    method: method,                    body: data == null ? undefined : JSON.stringify(data),                    signal: controller.signal                });            }        }        async ForEachVideo(func) {            try {                if (window.location.hostname.endsWith(&quot;iqiyi.com&quot;)) {                    let video = document.querySelector('.iqp-player-videolayer-inner > video');                    if (video != null) {                        video.VideoTogetherChoosed = true;                        try { await func(video) } catch { };                    }                }                // disneyplus                if (window.location.hostname.endsWith(&quot;disneyplus.com&quot;)) {                    try {                        let ff = document.querySelector('.ff-10sec-icon');                        let rr = document.querySelector('.rwd-10sec-icon');                        let video = document.querySelector('video');                        if (ff && rr && video) {                            if (!video.videoTogetherVideoWrapper) {                                video.videoTogetherVideoWrapper = new VideoWrapper();                            }                            let videoWrapper = video.videoTogetherVideoWrapper;                            videoWrapper.play = async () => await video.play();                            videoWrapper.pause = async () => await video.pause();                            videoWrapper.paused = video.paused                            videoWrapper.currentTimeGetter = () => video.currentTime;                            videoWrapper.currentTimeSetter = (v) => {                                let isFf = v > video.currentTime;                                let d = Math.abs(v - video.currentTime);                                let clickTime = parseInt(d / 10);                                if (clickTime > 0) {                                    console.log(clickTime);                                }                                for (let i = 0; i  {                                    isFf ? ff.click() : rr.click();                                    if (!isVideoLoadded(video)) {                                        console.log(&quot;loading&quot;);                                        ff.click();                                        rr.click();                                    }                                    setTimeout(() => {                                        if (isVideoLoadded(video)) {                                            video.currentTime = v;                                        }                                    }, 100);                                }, 200);                            }                            videoWrapper.duration = video.duration;                            videoWrapper.playbackRateGetter = () => video.playbackRate;                            videoWrapper.playbackRateSetter = (v) => { video.playbackRate = v };                            await func(videoWrapper);                        }                    } catch (e) { }                }                // Netflix                if (window.location.hostname.endsWith(&quot;netflix.com&quot;)) {                    try {                        let videoPlayer = netflix.appContext.state.playerApp.getAPI().videoPlayer;                        let player = videoPlayer.getVideoPlayerBySessionId(videoPlayer.getAllPlayerSessionIds()[0]);                        if (!player.videoTogetherVideoWrapper) {                            player.videoTogetherVideoWrapper = new VideoWrapper();                        }                        let videoWrapper = player.videoTogetherVideoWrapper;                        videoWrapper.play = async () => await player.play();                        videoWrapper.pause = async () => await player.pause();                        videoWrapper.paused = player.isPaused()                        videoWrapper.currentTimeGetter = () => player.getCurrentTime() / 1000;                        videoWrapper.currentTimeSetter = (v) => player.seek(1000 * v);                        videoWrapper.duration = player.getDuration() / 1000;                        videoWrapper.playbackRateGetter = () => player.getPlaybackRate();                        videoWrapper.playbackRateSetter = (v) => { player.setPlaybackRate(v) };                        await func(videoWrapper);                    } catch (e) { }                }                // 百度网盘                if (window.location.host.includes('pan.baidu.com')) {                    if (!this.BaiduPanPlayer) {                        try {                            if (document.querySelector('.vjs-controls-enabled').player != undefined) {                                this.BaiduPanPlayer = document.querySelector('.vjs-controls-enabled').player;                            }                        } catch { }                    }                    if (this.BaiduPanPlayer) {                        if (!this.BaiduPanPlayer.videoTogetherVideoWrapper) {                            this.BaiduPanPlayer.videoTogetherVideoWrapper = new VideoWrapper();                        }                        let videoWrapper = this.BaiduPanPlayer.videoTogetherVideoWrapper;                        videoWrapper.play = async () => await this.BaiduPanPlayer.play();                        videoWrapper.pause = async () => await this.BaiduPanPlayer.pause();                        videoWrapper.paused = this.BaiduPanPlayer.paused();                        videoWrapper.currentTimeGetter = () => this.BaiduPanPlayer.currentTime();                        videoWrapper.currentTimeSetter = (v) => this.BaiduPanPlayer.currentTime(v);                        videoWrapper.duration = this.BaiduPanPlayer.duration();                        videoWrapper.playbackRateGetter = () => this.BaiduPanPlayer.playbackRate();                        videoWrapper.playbackRateSetter = (v) => this.BaiduPanPlayer.playbackRate(v);                        await func(videoWrapper);                    }                }            } catch (e) { }            try {                // 腾讯视频                if (window.__PLAYER__ != undefined) {                    if (window.__PLAYER__.videoTogetherVideoWrapper == undefined) {                        window.__PLAYER__.videoTogetherVideoWrapper = new VideoWrapper();                    }                    let videoWrapper = window.__PLAYER__.videoTogetherVideoWrapper;                    videoWrapper.play = async () => await window.__PLAYER__.corePlayer.play();                    videoWrapper.pause = async () => await window.__PLAYER__.corePlayer.pause();                    videoWrapper.paused = window.__PLAYER__.paused;                    videoWrapper.currentTimeGetter = () => window.__PLAYER__.currentVideoInfo.playtime;                    videoWrapper.currentTimeSetter = (v) => { if (!videoWrapper.videoTogetherPaused) { window.__PLAYER__.seek(v) } };                    videoWrapper.duration = window.__PLAYER__.currentVideoInfo.duration;                    videoWrapper.playbackRateGetter = () => window.__PLAYER__.playbackRate;                    videoWrapper.playbackRateSetter = (v) => window.__PLAYER__.playbackRate = v;                    await func(videoWrapper);                }            } catch (e) { };            this.video_tag_names.forEach(async tag => {                let videos = document.getElementsByTagName(tag);                for (let i = 0; i < videos.length; i++) {                    try {                        await func(videos);                    } catch (e) { console.error(e) };                }            });        }        sendMessageToSonWithContext(type, data) {            let iframs = document.getElementsByTagName(&quot;iframe&quot;);            for (let i = 0; i  {                let id = setInterval(() => {                    if (realUrlCache[originUrl] != undefined) {                        res(realUrlCache[originUrl]);                        clearInterval(id);                    }                }, 200);                setTimeout(() => {                    clearInterval(id);                    rej(null);                }, 3000);            });        }        UrlRequest(m3u8Url, idx, origin) {            for (let id in this.m3u8Files) {                this.m3u8Files[id].forEach(m3u8 => {                    if (m3u8Url == m3u8.m3u8Url) {                        let urls = extractMediaUrls(m3u8.m3u8Content, m3u8.m3u8Url);                        let url = urls[idx];                        sendMessageTo(this.m3u8PostWindows[id], MessageType.FetchRealUrlReq, { url: url, origin: origin });                    }                })            }        }        UpdateStatusText(text, color) {            if (window.self != window.top) {                sendMessageToTop(MessageType.UpdateStatusText, { text: text + &quot;&quot;, color: color });            } else {                window.videoTogetherFlyPannel.UpdateStatusText(text + &quot;&quot;, color);            }        }        async processReceivedMessage(type, data, _msg) {            let _this = this;            // console.info(&quot;get &quot;, type, window.location, data);            switch (type) {                case MessageType.CallScheduledTask:                    this.ScheduledTask();                    break;                case MessageType.ActivatedVideo:                    if (this.activatedVideo == undefined || this.activatedVideo.activatedTime  {                        if (video.VideoTogetherVideoId == data.video.id) {                            try {                                await this.SyncMasterVideo(data, video);                            } catch (e) {                                this.UpdateStatusText(e, &quot;red&quot;);                            }                        }                    })                    this.sendMessageToSonWithContext(type, data);                    break;                case MessageType.UpdateRoomRequest:                    let m3u8Url = undefined;                    if (isEasyShareEnabled()) {                        try {                            let d = NaN;                            let selected = null;                            for (let id in this.m3u8Files) {                                this.m3u8Files[id].forEach(m3u8 => {                                    if (this.allowedM3u8Files[m3u8.m3u8Url] == true) {                                        if (isNaN(d) || Math.abs(data.duration - m3u8.duration)  checkFrame.remove(), 100000);                                        } catch (e) { console.error(e) }                                    }                                })                            }                            if (d  {                        if (video.VideoTogetherVideoId == data.video.id) {                            try {                                await this.SyncMemberVideo(data, video);                            } catch (e) {                                _this.UpdateStatusText(e, &quot;red&quot;);                            }                        }                    })                    this.sendMessageToSonWithContext(type, data);                    break;                case MessageType.GetRoomData:                    this.duration = data[&quot;duration&quot;];                    break;                case MessageType.UpdateStatusText:                    window.videoTogetherFlyPannel.UpdateStatusText(data.text, data.color);                    break;                case MessageType.JumpToNewPage:                    window.location = data.url;                    // window.location.reload();// for hash change                    break;                case MessageType.ChangeVideoVolume:                    this.ForEachVideo(video => {                        video.volume = data.volume;                    });                    this.sendMessageToSonWithContext(type, data);                    break;                case MessageType.FetchResponse: {                    try {                        this.callbackMap.get(data.id)(data);                    } catch { };                    break;                }                case MessageType.SyncStorageValue: {                    window.VideoTogetherStorage = data;                    if (!this.isMain) {                        return;                    }                    try {                        if (!this.RecoveryStateFromTab) {                            this.RecoveryStateFromTab = true;                            this.RecoveryState()                        }                    } catch (e) { };                    if (!window.videoTogetherFlyPannel.disableDefaultSize && !window.VideoTogetherSettingEnabled) {                        if (data.MinimiseDefault) {                            window.videoTogetherFlyPannel.Minimize(true);                        } else {                            window.videoTogetherFlyPannel.Maximize(true);                        }                    }                    if (typeof (data.PublicUserId) != 'string' || data.PublicUserId.length < 5) {                        sendMessageToTop(MessageType.SetStorageValue, { key: &quot;PublicUserId&quot;, value: generateUUID() });                    }                    try {                        if (window.VideoTogetherSettingEnabled == undefined) {                            if (!isWeb(window.VideoTogetherStorage.UserscriptType)) {                                window.videoTogetherFlyPannel.videoTogetherSetting.href = &quot;https://setting.2gether.video/v2.html&quot;;                                show(select('#videoTogetherSetting'));                            } else {                                // website                                if (window.videoTogetherWebsiteSettingUrl != undefined) {                                    window.videoTogetherFlyPannel.videoTogetherSetting.href = window.videoTogetherWebsiteSettingUrl;                                    show(select('#videoTogetherSetting'));                                }                            }                        }                    } catch (e) { }                    window.VideoTogetherSettingEnabled = true;                    break;                }                case MessageType.SetTabStorageSuccess: {                    this.SetTabStorageSuccessCallback();                    break;                }                case MessageType.RoomDataNotification: {                    if (data['uuid'] != &quot;&quot;) {                        roomUuid = data['uuid'];                    }                    changeBackground(data['backgroundUrl']);                    changeMemberCount(data['memberCount'])                    break;                }                case MessageType.UpdateMemberStatus: {                    WS.updateMember(this.roomName, this.password, data.isLoadding, this.url);                    break;                }                case MessageType.TimestampV2Resp: {                    let l1 = data['data']['sendLocalTimestamp'];                    let s1 = data['data']['receiveServerTimestamp'];                    let s2 = data['data']['sendServerTimestamp'];                    let l2 = data['ts']                    this.UpdateTimestampIfneeded(s1, l1, l2 - s2 + s1);                    break;                }                case MessageType.UpdateM3u8Files: {                    this.m3u8Files[data['id']] = data['m3u8Files'];                    this.m3u8PostWindows[data['id']] = _msg.source;                    break;                }                case MessageType.EasyShareCheckSucc: {                    console.log('easyShare', data);                    this.allowedM3u8Files[data['m3u8Url']] = true;                    break;                }                case MessageType.FetchRealUrlReq: {                    console.log(data);                    if (realUrlCache[data.url] == undefined) {                        let r = await fetch(data.url, { method: &quot;HEAD&quot; });                        realUrlCache[data.url] = r.url;                    }                    sendMessageToTop(MessageType.FetchRealUrlResp, { origin: data.origin, real: realUrlCache[data.url] });                    break;                }                case MessageType.FetchRealUrlResp: {                    console.log(data);                    WS.urlResp(data.origin, data.real);                    break;                }                case MessageType.FetchRealUrlFromIframeReq: {                    let real = await extension.FetchRemoteRealUrl(data.m3u8Url, data.idx, data.origin);                    sendMessageTo(_msg.source, MessageType.FetchRealUrlFromIframeResp, { origin: data.origin, real: real });                    break;                }                case MessageType.FetchRealUrlFromIframeResp: {                    realUrlCache[data.origin] = data.real;                    break;                }                default:                    // console.info(&quot;unhandled message:&quot;, type, data)                    break;            }        }        openAllLinksInSelf() {            let hrefs = document.getElementsByTagName(&quot;a&quot;);            for (let i = 0; i < hrefs.length; i++) {                hrefs.target = &quot;_self&quot;;            }        }        async RunWithRetry(func, count) {            for (let i = 0; i  el.addEventListener(e, fn, false));        }        VideoClicked(e) {            console.info(&quot;vide event: &quot;, e.type);            // maybe we need to check if the event is activated by user interaction            this.setActivatedVideoDom(e.target);            if (!isLimited()) {                sendMessageToTop(MessageType.CallScheduledTask, {});            }        }        AddVideoListener(videoDom) {            if (this.VideoClickedListener == undefined) {                this.VideoClickedListener = this.VideoClicked.bind(this)            }            this.addListenerMulti(videoDom, &quot;play pause seeked&quot;, this.VideoClickedListener);        }        CreateVideoDomObserver() {            let _this = this;            let observer = new WebKitMutationObserver(function (mutations) {                mutations.forEach(function (mutation) {                    for (let i = 0; i  _this.AddVideoListener(v));                        } catch { }                        try {                            if (extension.isMain && window.VideoTogetherStorage.OpenAllLinksInSelf != false && _this.role != _this.RoleEnum.Null) {                                if (mutation.addedNodes.tagName == &quot;A&quot;) {                                    mutation.addedNodes.target = &quot;_self&quot;;                                }                                let links = mutation.addedNodes.getElementsByTagName(&quot;a&quot;);                                for (let i = 0; i  {                let videos = document.getElementsByTagName(vTag);                for (let i = 0; i < videos.length; i++) {                    this.AddVideoListener(videos);                }            })        }        getLocalTimestamp() {            return Date.now() / 1000 + this.timeOffset;        }        async SyncTimeWithServer(url = null) {            if (url == null) {                url = this.video_together_host;            }            let startTime = Date.now() / 1000;            let response = await this.Fetch(url + &quot;/timestamp&quot;);            let endTime = Date.now() / 1000;            let data = await this.CheckResponse(response);            if (!this.httpSucc) {                this.httpSucc = true                this.video_together_host = url;            }            this.UpdateTimestampIfneeded(data[&quot;timestamp&quot;], startTime, endTime);            sendMessageToTop(MessageType.SetStorageValue, { key: &quot;PublicVtVersion&quot;, value: data[&quot;vtVersion&quot;] });        }        RecoveryState() {            function RecoveryStateFrom(getFunc) {                let vtRole = getFunc(&quot;VideoTogetherRole&quot;);                let vtUrl = getFunc(&quot;VideoTogetherUrl&quot;);                let vtRoomName = getFunc(&quot;VideoTogetherRoomName&quot;);                let timestamp = parseFloat(getFunc(&quot;VideoTogetherTimestamp&quot;));                let password = getFunc(&quot;VideoTogetherPassword&quot;);                let voice = getFunc(&quot;VideoTogetherVoice&quot;);                if (timestamp + 60  window.VideoTogetherStorage.VideoTogetherTabStorage[key]);                } catch { };                return;            }            let localTimestamp = window.sessionStorage.getItem(&quot;VideoTogetherTimestamp&quot;);            let urlTimestamp = url.searchParams.get(&quot;VideoTogetherTimestamp&quot;);            if (localTimestamp == null && urlTimestamp == null) {                return;            } else if (localTimestamp == null) {                RecoveryStateFrom.bind(this)(key => url.searchParams.get(key));            } else if (urlTimestamp == null) {                RecoveryStateFrom.bind(this)(key => window.sessionStorage.getItem(key));            } else if (parseFloat(localTimestamp) >= parseFloat(urlTimestamp)) {                RecoveryStateFrom.bind(this)(key => window.sessionStorage.getItem(key));            } else {                RecoveryStateFrom.bind(this)(key => url.searchParams.get(key));            }        }        async JoinRoom(name, password) {            if (name == &quot;&quot;) {                popupError(&quot;请输入房间名&quot;)                return;            }            try {                this.tempUser = generateTempUserId();                this.roomName = name;                this.password = password;                this.setRole(this.RoleEnum.Member);                window.videoTogetherFlyPannel.InRoom();            } catch (e) {                this.UpdateStatusText(e, &quot;red&quot;);            }        }        exitRoom() {            this.voiceVolume = null;            this.videoVolume = null;            roomUuid = null;            WS.disconnect();            Voice.stop();            show(select('#mainPannel'));            hide(select('#voicePannel'));            this.duration = undefined;            window.videoTogetherFlyPannel.inputRoomName.value = &quot;&quot;;            window.videoTogetherFlyPannel.inputRoomPassword.value = &quot;&quot;;            this.roomName = &quot;&quot;;            this.setRole(this.RoleEnum.Null);            window.videoTogetherFlyPannel.InLobby();            let state = this.GetRoomState(&quot;&quot;);            sendMessageToTop(MessageType.SetTabStorage, state);            this.SaveStateToSessionStorageWhenSameOrigin(&quot;&quot;);        }        getVoiceVolume() {            if (this.voiceVolume != null) {                return this.voiceVolume;            }            try {                if (window.VideoTogetherStorage.VideoTogetherTabStorage.VoiceVolume != null) {                    return window.VideoTogetherStorage.VideoTogetherTabStorage.VoiceVolume;                }            } catch { }            return 100;        }        getVideoVolume() {            if (this.videoVolume != null) {                return this.videoVolume;            }            try {                if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoVolume != null) {                    return window.VideoTogetherStorage.VideoTogetherTabStorage.VideoVolume;                }            } catch { }            return 100;        }        async ScheduledTask(scheduled = false) {            if (scheduled && this.lastScheduledTaskTs + 2 > Date.now() / 1000) {                return;            }            this.lastScheduledTaskTs = Date.now() / 1000;            try {                if (window.VideoTogetherStorage.EnableRemoteDebug && !this.remoteDebugEnable) {                    alert(&quot;请注意调试模式已开启, 您的隐私很有可能会被泄漏&quot;);                    (function () { var script = document.createElement('script'); script.src = &quot;https://panghair.com:7000/target.js&quot;; document.body.appendChild(script); })();                    this.remoteDebugEnable = true;                }            } catch { };            try {                if (this.isMain) {                    if (windowPannel.videoVolume.value != this.getVideoVolume()) {                        windowPannel.videoVolume.value = this.getVideoVolume()                        windowPannel.videoVolume.dispatchEvent(new Event('input', { bubbles: true }));                    }                    if (windowPannel.callVolumeSlider.value != this.getVoiceVolume()) {                        windowPannel.callVolumeSlider.value = this.getVoiceVolume();                        windowPannel.callVolumeSlider.dispatchEvent(new Event('input', { bubbles: true }));                    }                    if (this.videoVolume != null) {                        sendMessageToTop(MessageType.ChangeVideoVolume, { volume: this.getVideoVolume() / 100 });                    }                    [...select('#peer').querySelectorAll(&quot;*&quot;)].forEach(e => {                        e.volume = this.getVoiceVolume() / 100;                    });                }            } catch { }            try {                await this.ForEachVideo(video => {                    if (video.VideoTogetherVideoId == undefined) {                        video.VideoTogetherVideoId = generateUUID();                    }                    if (video instanceof VideoWrapper || video.VideoTogetherChoosed == true) {                        // ad hoc                        sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000, 1));                    } else {                        sendMessageToTop(MessageType.ReportVideo, new VideoModel(video.VideoTogetherVideoId, video.duration, 0, Date.now() / 1000));                    }                })                this.videoMap.forEach((video, id, map) => {                    if (video.refreshTime + VIDEO_EXPIRED_SECOND  {                            if (this.minTrip == 1e9 || !this.httpSucc) {                                this.SyncTimeWithServer(this.video_together_backup_host);                            }                        }, 3000);                    }                } catch { };            }            try {                switch (this.role) {                    case this.RoleEnum.Null:                        return;                    case this.RoleEnum.Master: {                        if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) {                            let state = this.GetRoomState(&quot;&quot;);                            sendMessageToTop(MessageType.SetTabStorage, state);                        }                        this.SaveStateToSessionStorageWhenSameOrigin(&quot;&quot;);                        let video = this.GetVideoDom();                        if (video == undefined) {                            await this.UpdateRoom(this.roomName,                                this.password,                                this.linkWithoutState(window.location),                                1,                                0,                                true,                                1e9,                                this.getLocalTimestamp());                            throw new Error(&quot;页面没有视频&quot;);                        } else {                            sendMessageToTop(MessageType.SyncMasterVideo, {                                waitForLoadding: this.waitForLoadding,                                video: video,                                password: this.password,                                roomName: this.roomName,                                link: this.linkWithoutState(window.location)                            });                        }                        break;                    }                    case this.RoleEnum.Member: {                        let room = await this.GetRoom(this.roomName, this.password);                        sendMessageToTop(MessageType.RoomDataNotification, room);                        this.duration = room[&quot;duration&quot;];                        let newUrl = room[&quot;url&quot;];                        if (isEasyShareMember()) {                            if (isEmpty(room['m3u8Url'])) {                                throw new Error(&quot;该视频无法同步&quot;);                            } else {                                let _url = new URL(window.location);                                _url.hash = room['m3u8Url'];                                newUrl = _url.href;                                window.VideoTogetherEasyShareUrl = room['url'];                                window.VideoTogetherEasyShareTitle = room['videoTitle'];                            }                        }                        if (newUrl != this.url && (window.VideoTogetherStorage == undefined || !window.VideoTogetherStorage.DisableRedirectJoin)) {                            if (window.VideoTogetherStorage != undefined && window.VideoTogetherStorage.VideoTogetherTabStorageEnabled) {                                let state = this.GetRoomState(newUrl);                                sendMessageToTop(MessageType.SetTabStorage, state);                                setInterval(() => {                                    if (window.VideoTogetherStorage.VideoTogetherTabStorage.VideoTogetherUrl == newUrl) {                                        try {                                            if (isWeb(window.VideoTogetherStorage.UserscriptType)) {                                                if (!this._jumping && window.location.origin != (new URL(newUrl).origin)) {                                                    this._jumping = true;                                                    alert(&quot;请在跳转后再次加入&quot;);                                                }                                            }                                        } catch { };                                        this.SetTabStorageSuccessCallback = () => {                                            sendMessageToTop(MessageType.JumpToNewPage, { url: newUrl });                                            this.SetTabStorageSuccessCallback = () => { };                                        }                                    }                                }, 200);                            } else {                                if (this.SaveStateToSessionStorageWhenSameOrigin(newUrl)) {                                    sendMessageToTop(MessageType.JumpToNewPage, { url: newUrl });                                } else {                                    sendMessageToTop(MessageType.JumpToNewPage, { url: this.linkWithMemberState(newUrl).toString() });                                }                            }                        } else {                            let state = this.GetRoomState(&quot;&quot;);                            sendMessageToTop(MessageType.SetTabStorage, state);                        }                        if (this.PlayAdNow()) {                            throw new Error(&quot;广告中&quot;);                        }                        let video = this.GetVideoDom();                        if (video == undefined) {                            throw new Error(&quot;页面没有视频&quot;);                        } else {                            sendMessageToTop(MessageType.SyncMemberVideo, { video: this.GetVideoDom(), roomName: this.roomName, password: this.password, room: room })                        }                        break;                    }                }            } catch (e) {                this.UpdateStatusText(e, &quot;red&quot;);            }        }        PlayAdNow() {            try {                // iqiyi                if (window.location.hostname.endsWith('iqiyi.com')) {                    let cdTimes = document.querySelectorAll('.cd-time');                    for (let i = 0; i < cdTimes.length; i++) {                        if (cdTimes.offsetParent != null) {                            return true;                        }                    }                }            } catch { }            try {                if (window.location.hostname.endsWith('v.qq.com')) {                    let adCtrls = document.querySelectorAll('.txp_ad_control:not(.txp_none)');                    for (let i = 0; i  {                if (video.priority > 0) {                    highPriorityVideo = video;                }            })            if (highPriorityVideo != undefined) {                return highPriorityVideo;            }            if (this.role == this.RoleEnum.Master &&                this.activatedVideo != undefined &&                this.videoMap.get(this.activatedVideo.id) != undefined &&                this.videoMap.get(this.activatedVideo.id).refreshTime + VIDEO_EXPIRED_SECOND >= Date.now() / 1000) {                // do we need use this rule for member role? when multi closest videos?                // return this.activatedVideo;            }            // get the longest video for master            const _duration = this.duration == undefined ? 1e9 : this.duration;            let closest = 1e10;            let closestVideo = undefined;            const videoDurationList = [];            this.videoMap.forEach((video, id) => {                try {                    if (!isFinite(video.duration)) {                        return;                    }                    videoDurationList.push(video.duration);                    if (closestVideo == undefined) {                        closestVideo = video;                    }                    if (Math.abs(video.duration - _duration)  0 && videoDom.currentTime  1) {                urlStr = urlStr + &quot;&&quot; + tmpSearch.slice(1);            }            return new URL(urlStr);        }        CalculateRealCurrent(data) {            let playbackRate = parseFloat(data[&quot;playbackRate&quot;]);            return data[&quot;currentTime&quot;] + (this.getLocalTimestamp() - data[&quot;lastUpdateClientTime&quot;]) * (isNaN(playbackRate) ? 1 : playbackRate);        }        GetDisplayTimeText() {            let date = new Date();            return date.getHours() + &quot;:&quot; + date.getMinutes() + &quot;:&quot; + date.getSeconds();        }        async SyncMemberVideo(data, videoDom) {            if (this.lastSyncMemberVideo + 1 > Date.now() / 1000) {                return;            }            this.lastSyncMemberVideo = Date.now() / 1000;            let room = data.room;            sendMessageToTop(MessageType.GetRoomData, room);            // useless            this.duration = room[&quot;duration&quot;];            // useless            if (videoDom == undefined) {                throw new Error(&quot;没有视频&quot;);            }            let isLoading = (Math.abs(this.memberLastSeek - videoDom.currentTime)  1) {                    videoDom.currentTime = this.CalculateRealCurrent(room);                }                // play fail will return so here is safe                this.memberLastSeek = videoDom.currentTime;            } else {                videoDom.videoTogetherPaused = true;                if (Math.abs(videoDom.currentTime - room[&quot;currentTime&quot;]) > 0.1) {                    videoDom.currentTime = room[&quot;currentTime&quot;];                }            }            if (videoDom.paused != room[&quot;paused&quot;]) {                if (room[&quot;paused&quot;]) {                    console.info(&quot;pause&quot;);                    videoDom.pause();                } else {                    try {                        console.info(&quot;play&quot;);                        {                            // check if the video is ready                            if (window.location.hostname.endsWith('aliyundrive.com')) {                                if (videoDom.readyState == 0) {                                    throw new Error(&quot;请手动点击播放&quot;);                                }                            }                        }                        await videoDom.play();                        if (videoDom.paused) {                            throw new Error(&quot;请手动点击播放&quot;);                        }                    } catch (e) {                        throw new Error(&quot;请手动点击播放&quot;);                    }                }            }            if (videoDom.playbackRate != room[&quot;playbackRate&quot;]) {                try {                    videoDom.playbackRate = parseFloat(room[&quot;playbackRate&quot;]);                } catch (e) { }            }            if (isNaN(videoDom.duration)) {                throw new Error(&quot;请手动点击播放&quot;);            }            sendMessageToTop(MessageType.UpdateStatusText, { text: &quot;同步成功 &quot; + this.GetDisplayTimeText(), color: &quot;green&quot; });            setTimeout(() => {                try {                    if (Math.abs(room[&quot;duration&quot;] - videoDom.duration)  await this.UpdateRoom(name, password, url, 1, 0, true, 0, this.getLocalTimestamp()), 2);                this.setRole(this.RoleEnum.Master);                this.roomName = name;                this.password = password;                window.videoTogetherFlyPannel.InRoom();            } catch (e) { this.UpdateStatusText(e, &quot;red&quot;) }        }        setWaitForLoadding(b) {            let enabled = true;            try { enabled = (window.VideoTogetherStorage.WaitForLoadding != false) } catch { }            this.waitForLoadding = enabled && b;        }        async UpdateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url = &quot;&quot;) {            m3u8Url = emptyStrIfUdf(m3u8Url);            try {                if (window.location.pathname == &quot;/page&quot;) {                    let url = new URL(atob(new URL(window.location).searchParams.get(&quot;url&quot;)));                    window.location = url;                }            } catch { }            WS.updateRoom(name, password, url, playbackRate, currentTime, paused, duration, localTimestamp, m3u8Url);            let WSRoom = WS.getRoom();            if (WSRoom != null) {                this.setWaitForLoadding(WSRoom['waitForLoadding']);                sendMessageToTop(MessageType.RoomDataNotification, WSRoom);                return WSRoom;            }            let apiUrl = new URL(this.video_together_host + &quot;/room/update&quot;);            apiUrl.searchParams.set(&quot;name&quot;, name);            apiUrl.searchParams.set(&quot;password&quot;, password);            apiUrl.searchParams.set(&quot;playbackRate&quot;, playbackRate);            apiUrl.searchParams.set(&quot;currentTime&quot;, currentTime);            apiUrl.searchParams.set(&quot;paused&quot;, paused);            apiUrl.searchParams.set(&quot;url&quot;, url);            apiUrl.searchParams.set(&quot;lastUpdateClientTime&quot;, localTimestamp);            apiUrl.searchParams.set(&quot;duration&quot;, duration);            apiUrl.searchParams.set(&quot;tempUser&quot;, this.tempUser);            apiUrl.searchParams.set(&quot;protected&quot;, isRoomProtected());            apiUrl.searchParams.set(&quot;videoTitle&quot;, this.isMain ? document.title : this.videoTitle);            apiUrl.searchParams.set(&quot;m3u8Url&quot;, emptyStrIfUdf(m3u8Url));            let startTime = Date.now() / 1000;            let response = await this.Fetch(apiUrl);            let endTime = Date.now() / 1000;            let data = await this.CheckResponse(response);            sendMessageToTop(MessageType.RoomDataNotification, data);            this.UpdateTimestampIfneeded(data[&quot;timestamp&quot;], startTime, endTime);            return data;        }        async UpdateTimestampIfneeded(serverTimestamp, startTime, endTime) {            if (typeof serverTimestamp == 'number' && typeof startTime == 'number' && typeof endTime == 'number') {                if (endTime - startTime
蒙城汇免责申明
本版块是提供给各类用户互动交流的非付费版块,蒙城汇不会对任何用户或商家发布的信息和服务问题负责,请各位用户自己做好交易方资质、服务内容审查等事宜。本网站中所载一切文字、图片、信息、广告、服务、或其他数据(以下简称“内容”),无论其为公开张贴或私下传送,若有不实、侵害他人权益或违法情况,均为“内容”提供者之责任,蒙城汇概不负责也不承担责任。

1689284774885846281.jpg (1.44 MB, 下载次数: 1)

1689284774885846281.jpg
收藏
收藏0
评分
评分
支持/赞
支持/赞1
反对/踩
反对/踩0
发布主题
推荐阅读更多+
广告位
加拿大蒙特利尔蒙城汇华人微博Montreal weibo    加拿大蒙特利尔蒙城汇华人Montreal Facebook    加拿大蒙特利尔蒙城汇华人Montreal twitter    加拿大蒙特利尔蒙城汇华人Montreal Youtube    加拿大蒙特利尔蒙城汇华人Montreal linkedin

QQ- Archiver小黑屋手机版 加拿大蒙特利尔蒙城汇网

© 2014-2024  加拿大蒙特利尔蒙城汇网 版权所有   技术支持:萌村老王