Browse Source

change voicechat from jitsi to kraken

Dele Olajide 3 tháng trước cách đây
mục cha
commit
ef7f4816b5

+ 0 - 3
index.html

@@ -35,9 +35,6 @@
     <script src="packages/diagrams/abcjs.js"></script>
 	
     <link type="text/css" rel="stylesheet" media="screen" href="packages/voicechat/voicechat.css" />	
-    <script src="packages/voicechat/hark.js"></script>
-    <script src="packages/voicechat/jquery-3.5.1.min.js"></script>	
-    <script src="packages/voicechat/lib-jitsi-meet.min.js"></script>
     <script src="packages/voicechat/voicechat.js"></script>	
 
     <link type="text/css" rel="stylesheet" media="screen" href="packages/adaptive-cards/adaptivecards.css" />	

+ 0 - 0
packages/voicechat/callstats-ws.min.js → packages/voicechat-old/callstats-ws.min.js


+ 0 - 0
packages/voicechat/hark.js → packages/voicechat-old/hark.js


+ 0 - 0
packages/voicechat/jquery-3.5.1.min.js → packages/voicechat-old/jquery-3.5.1.min.js


+ 0 - 0
packages/voicechat/lib-jitsi-meet.min.js → packages/voicechat-old/lib-jitsi-meet.min.js


+ 0 - 0
packages/voicechat/lib-jitsi-meet.min.map → packages/voicechat-old/lib-jitsi-meet.min.map


+ 0 - 0
packages/voicechat/package.json → packages/voicechat-old/package.json


+ 0 - 0
packages/voicechat/readme.md → packages/voicechat-old/readme.md


+ 9 - 0
packages/voicechat-old/voicechat.css

@@ -0,0 +1,9 @@
+.blink_me {
+  animation: blinker 1s linear infinite;
+}
+
+@keyframes blinker {
+  50% {
+    opacity: 0;
+  }
+}

+ 369 - 0
packages/voicechat-old/voicechat.js

@@ -0,0 +1,369 @@
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(["converse"], factory);
+    } else {
+        factory(converse);
+    }
+}(this, function (converse) {
+    let _converse, html, __, model, harker, connection, button, room, jitsiRoom, localTracks, remoteTracks = {}, recognition, recognitionActive, converseConn;
+
+	converse.plugins.add("voicechat", {
+		dependencies: [],
+
+		initialize: function () {
+            _converse = this._converse;
+            html = converse.env.html;
+            __ = _converse.__;
+
+			_converse.api.settings.extend({
+				voicechat: {
+					hosts: {
+						domain: 'beta.meet.jit.si',
+						muc: 'conference.beta.meet.jit.si'
+					},					
+					serviceUrl: 'https://beta.meet.jit.si/http-bind',
+					prefix: 'VC',					
+					transcribe: false,
+					transcribeLanguage: 'en-GB',
+					start:  __('Start Voice Chat'),
+					stop: __('Stop Voice Chat'),
+					started: __('has started speaking'),
+					stopped: __('has stopped speaking')					
+				}
+			});
+			
+			_converse.api.listen.on('connected', async function() {	
+				converseConn = await _converse.api.connection.get();
+			});				
+			
+            _converse.api.listen.on('getToolbarButtons', function(toolbar_el, buttons)
+            {
+				let style = "width:18px; height:18px; fill:var(--chat-color);";
+				if (toolbar_el.model.get("type") === "chatroom") {
+					style = "width:18px; height:18px; fill:var(--muc-color);";
+				}
+
+                buttons.push(html`
+                    <button class="btn plugin-voicechat" title="${_converse.api.settings.get('voicechat').start}" @click=${performAudio}/>
+						<svg style="${style}" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="volume-up" class="svg-inline--fa fa-volume-up fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"></path></svg>					
+                    </button>
+                `);
+
+                return buttons;
+            });			
+
+            _converse.api.listen.on('chatRoomViewInitialized', function (view)
+            {
+                console.debug("chatRoomViewInitialized", view);
+				stopVoiceChat();
+			});
+			
+            _converse.api.listen.on('chatBoxViewInitialized', function (view)
+            {
+                console.debug("chatBoxViewInitialized", view);
+				stopVoiceChat();
+			});
+			
+            _converse.api.listen.on('chatBoxClosed', function (model)
+            {
+                console.debug("chatBoxClosed", model);
+				stopVoiceChat();
+            });			
+			
+			console.log("voicechat plugin is ready");
+		}
+	});
+
+	function 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) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
+		h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
+		return 4294967296 * (2097151 & h2) + (h1>>>0);
+	}
+	
+	function performAudio(ev) {
+        ev.stopPropagation();
+        ev.preventDefault();
+		
+		if (converseConn) {
+			const toolbar_el = converse.env.utils.ancestor(ev.target, 'converse-chat-toolbar');	
+			model = toolbar_el.model;
+			const type = (model.get('type') == 'chatroom') ? 'groupchat' : 'chat';				
+			const target = model.get('jid');
+			const myself = Strophe.getBareJidFromJid(converseConn.jid);
+											
+			room = _converse.api.settings.get('voicechat').prefix.toLocaleLowerCase() + cyrb53((model.get('type') == 'chatroom') ? target : (myself < target ? myself + target : target + myself));
+			button = toolbar_el.querySelector('.plugin-voicechat');
+
+			console.debug("voicechat is clicked", model, room, button);
+
+			if (button.classList.contains('blink_me')) {
+				stopVoiceChat(model);						
+			} else {
+				startVoiceChat();						
+			}	
+		}			
+	}
+
+	function stopVoiceChat() {			
+		if (localTracks){
+			for (let i = 0; i < localTracks.length; i++) {
+				try {				
+					localTracks[i].dispose();
+				} catch (e) {};
+			}
+			localTracks = null;			
+		}
+		
+		if (jitsiRoom) {
+			jitsiRoom.leave();
+			jitsiRoom = null;
+		}
+		
+		if (connection) {
+			connection.disconnect();
+		}
+		let voiceChatAudio = document.getElementById('voicechat-audio');
+		if (voiceChatAudio) voiceChatAudio.innerHTML = "";	
+		
+		if (button && button.classList.contains('blink_me')) {
+			button.classList.remove('blink_me');
+			button.title = _converse.api.settings.get('voicechat').start;
+			model.sendMessage({body: '/me ' + _converse.api.settings.get('voicechat').stopped});			
+		}
+		
+		if (recognitionActive && recognition)
+		{
+			recognition.stop();
+			recognitionActive = false;
+		}
+		
+		if (harker) {
+			harker.stop();
+		}
+	}
+	
+	function startVoiceChat() {			
+		if (!localTracks) {		
+			JitsiMeetJS.init({disableAudioLevels: true});
+			
+			JitsiMeetJS.createLocalTracks({ devices: [ 'audio'] }).then(onLocalTracks).catch(error => {
+				console.error("startVoiceChat", error);
+			});			
+		} else {
+			goConnect();
+		}	
+	}
+	
+	function goConnect() {
+		const options = _converse.api.settings.get('voicechat');			
+		console.debug("goConnect", room, localTracks, options);		
+		connection = new JitsiMeetJS.JitsiConnection(null, null, options);
+		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,  onConnectionSuccess);
+		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED,       onConnectionFailed);
+		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, onConnectionDisconnected);
+
+		JitsiMeetJS.mediaDevices.addEventListener(JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED, onDeviceListChanged);
+		connection.connect();			
+	}
+	
+	function getLocalStream() {
+		return document.getElementById('localAudio0')?.srcObject;	
+	}	
+	
+	function onLocalTracks(tracks) {
+		localTracks = tracks;		
+		console.debug("onLocalTracks", room, localTracks);
+		
+		let voiceChatAudio = document.getElementById('voicechat-audio');
+		if (!voiceChatAudio) voiceChatAudio = newElement('div', 'voicechat-audio');
+		
+		for (let i = 0; i < localTracks.length; i++) 
+		{
+			if (localTracks[i].getType() === 'audio') {			
+				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,	audioLevel => console.debug(`Audio Level local: ${audioLevel}`));
+				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_MUTE_CHANGED, () => console.debug('local track muted'));
+				localTracks[i].addEventListener(JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED,() => console.debug('local track stoped'));
+				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED, deviceId => {console.debug(`local track audio output device was changed to ${deviceId}`)	});
+
+				const localAudio = newElement('span', `local-audio-${i}`, `<audio autoplay='1' muted='true' id='localAudio${i}' />`);
+				voiceChatAudio.append(localAudio);
+				localTracks[i].attach($(`#localAudio${i}`)[0]);				
+			}
+			
+			if (i == 0) {
+
+				if (_converse.api.settings.get('voicechat').transcribe) {
+					setupSpeechRecognition();
+				}					
+
+				harker = hark(localTracks[i].stream, {interval: 100, history: 4 });
+
+				harker.on('speaking', () => {
+					
+					if (_converse.api.settings.get('voicechat').transcribe && model) {					
+						model.setChatState(_converse.COMPOSING);											
+					}
+				});
+
+				harker.on('stopped_speaking', () =>  {
+					
+					if (_converse.api.settings.get('voicechat').transcribe && model) {						
+						model.setChatState(_converse.PAUSED);												
+					}
+				});						
+			}
+		}
+		
+		goConnect();		
+	}
+
+	function onRemoteTrack(track) {
+		if (track.isLocal() || track.getType() === 'video') {
+			return;
+		}
+		let voiceChatAudio = document.getElementById('voicechat-audio');		
+		const participant = track.getParticipantId();
+
+		if (!remoteTracks[participant]) {
+			remoteTracks[participant] = [];
+		}
+		const idx = remoteTracks[participant].push(track);
+		console.debug("onRemoteTrack", room, remoteTracks);
+		
+		track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,	audioLevel => console.debug(`Audio Level remote: ${audioLevel}`));
+		track.addEventListener(JitsiMeetJS.events.track.TRACK_MUTE_CHANGED,	() => console.debug('remote track muted'));
+		track.addEventListener(JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED, () => console.debug('remote track stoped'));
+		track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,	deviceId =>	console.debug(`remote track audio output device was changed to ${deviceId}`));
+
+		const id = participant + track.getType() + idx;
+		const localAudio = newElement('span', `remote-audio-${participant}-${idx}`, `<audio autoplay='1' id='${participant}audio${idx}' />`);
+		voiceChatAudio.append(localAudio);		
+		track.attach($(`#${id}`)[0]);		
+	}
+	
+	function onDeviceListChanged(devices) {
+		console.info('onDeviceListChanged', devices);
+	}
+	
+	function onConnectionSuccess() {
+		console.debug("onConnectionSuccess", room);		
+		
+		jitsiRoom = connection.initJitsiConference(room, {});	
+		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
+		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_REMOVED, track => {console.debug(`track removed!!!${track}`)	});
+		jitsiRoom.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
+		jitsiRoom.on(JitsiMeetJS.events.conference.USER_JOINED, id => {console.debug('user join'); remoteTracks[id] = []	});
+		jitsiRoom.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
+		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_MUTE_CHANGED, track => {console.debug(`${track.getType()} - ${track.isMuted()}`)	});
+		jitsiRoom.on(JitsiMeetJS.events.conference.DISPLAY_NAME_CHANGED, (userID, displayName) => console.debug(`${userID} - ${displayName}`));
+		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_AUDIO_LEVEL_CHANGED, (userID, audioLevel) => console.debug(`${userID} - ${audioLevel}`));
+		jitsiRoom.on(JitsiMeetJS.events.conference.PHONE_NUMBER_CHANGED, () => console.debug(`${jitsiRoom.getPhoneNumber()} - ${jitsiRoom.getPhonePin()}`));
+		
+		jitsiRoom.join();
+	}
+
+	function onConnectionFailed() {
+		console.error('onConnectionFailed');
+	}
+
+	function onConnectionDisconnected() {
+		console.debug('onConnectionDisconnected!');
+		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,  onConnectionSuccess);
+		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED,       onConnectionFailed);
+		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, onConnectionDisconnected);
+	}
+
+	function onConferenceJoined() {
+		console.debug('onConferenceJoined!', jitsiRoom, localTracks);
+		
+		for (let i = 0; i < localTracks.length; i++) {
+			jitsiRoom.addTrack(localTracks[i]);
+		}
+		
+		if (button) {
+			button.classList.add('blink_me');	
+			button.title = _converse.api.settings.get('voicechat').stop;
+			model.sendMessage({body: '/me ' + _converse.api.settings.get('voicechat').started});			
+		}					
+	}
+
+	function onUserLeft(id) {
+		console.debug('onUserLeft', id);
+		
+		if (!remoteTracks[id]) {
+			return;
+		}
+		const tracks = remoteTracks[id];
+
+		for (let i = 0; i < tracks.length; i++) {
+			tracks[i].detach($(`#${id}${tracks[i].getType()}`));
+		}
+	}
+
+	function newElement(el, id, html, className) {
+		const ele = document.createElement(el);
+		if (id) ele.id = id;
+		if (html) ele.innerHTML = html;
+		if (className) ele.classList.add(className);
+		document.body.appendChild(ele);
+		return ele;
+	}
+	
+    function setupSpeechRecognition() {
+        console.debug("setupSpeechRecognition");
+
+        recognition = new webkitSpeechRecognition();
+        recognition.lang = _converse.api.settings.get('voicechat').transcribeLanguage;
+        recognition.continuous = true;
+        recognition.interimResults = false;
+
+        recognition.onresult = function(event)
+        {
+            console.debug("Speech recog event", event)
+
+            if (event.results[event.resultIndex].isFinal==true)
+            {
+                const transcript = event.results[event.resultIndex][0].transcript;
+                console.debug("Speech recog transcript", transcript);
+                if (model) model.sendMessage({'body': transcript});		
+				if (model) model.setChatState(_converse.ACTIVE);					
+			}
+        }
+
+        recognition.onspeechend  = function(event)
+        {
+            console.debug("Speech recog onspeechend", event);		
+        }
+
+        recognition.onstart = function(event)
+        {
+            console.debug("Speech to text started", event);
+            recognitionActive = true;			
+        }
+
+        recognition.onend = function(event)
+        {
+            console.debug("Speech to text ended", event);
+
+            if (recognitionActive)
+            {
+                console.debug("Speech to text restarted");
+                setTimeout(function() {recognition.start()}, 1000);
+            }
+        }
+
+        recognition.onerror = function(event)
+        {
+            console.debug("Speech to text error", event);
+        }
+
+        recognition.start();		
+    }	
+	
+}));

+ 0 - 0
packages/voicechat/voicechat.png → packages/voicechat-old/voicechat.png


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/0379545.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/27fa2ac.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
packages/voicechat/ohun/_nuxt/29a25cd.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
packages/voicechat/ohun/_nuxt/63409c8.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/768785d.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/7fa09b4.js


+ 50 - 0
packages/voicechat/ohun/_nuxt/LICENSES

@@ -0,0 +1,50 @@
+/*!
+ * Vue.js v2.7.16
+ * (c) 2014-2023 Evan You
+ * Released under the MIT License.
+ */
+
+/*!
+ * cookie
+ * Copyright(c) 2012-2014 Roman Shtylman
+ * Copyright(c) 2015 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+/*!
+ * vuex v3.6.2
+ * (c) 2021 Evan You
+ * @license MIT
+ */
+
+
+/*!
+ * clipboard.js v2.0.11
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+
+/*!
+ * vue-client-only v0.0.0-semantic-release
+ * (c) 2021-present egoist <0x142857@gmail.com>
+ * Released under the MIT License.
+ */
+
+/*!
+ * vue-i18n v8.28.2 
+ * (c) 2022 kazuya kawaguchi
+ * Released under the MIT License.
+ */
+
+/*!
+ * vue-no-ssr v1.1.1
+ * (c) 2018-present egoist <0x142857@gmail.com>
+ * Released under the MIT License.
+ */
+
+/**
+  * vue-class-component v7.2.6
+  * (c) 2015-present Evan You
+  * @license MIT
+  */

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/af024c0.js


+ 969 - 0
packages/voicechat/ohun/_nuxt/bb6d73d.js

@@ -0,0 +1,969 @@
+(window.webpackJsonp = window.webpackJsonp || []).push([
+    [4], {
+        510: function(t, e, r) {
+            "use strict";
+            var n = r(2),
+                o = r(22),
+                c = r(23),
+                l = r(30),
+                d = r(17),
+                h = r(35),
+                f = r(5),
+                m = (r(10), r(15), r(41), r(13), r(19), r(14), r(4), r(68), r(16), r(62), r(59), r(1)),
+                v = r(44),
+                y = r(73),
+                k = r(239);
+
+            function _(t, e) {
+                var r = Object.keys(t);
+                if (Object.getOwnPropertySymbols) {
+                    var n = Object.getOwnPropertySymbols(t);
+                    e && (n = n.filter((function(e) {
+                        return Object.getOwnPropertyDescriptor(t, e).enumerable
+                    }))), r.push.apply(r, n)
+                }
+                return r
+            }
+
+            function O() {
+                try {
+                    var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {})))
+                } catch (t) {}
+                return (O = function() {
+                    return !!t
+                })()
+            }
+            var w = function(t, e, r, desc) {
+                    var n, o = arguments.length,
+                        c = o < 3 ? e : null === desc ? desc = Object.getOwnPropertyDescriptor(e, r) : desc;
+                    if ("object" === ("undefined" == typeof Reflect ? "undefined" : Object(f.a)(Reflect)) && "function" == typeof Reflect.decorate) c = Reflect.decorate(t, e, r, desc);
+                    else
+                        for (var i = t.length - 1; i >= 0; i--)(n = t[i]) && (c = (o < 3 ? n(c) : o > 3 ? n(e, r, c) : n(e, r)) || c);
+                    return o > 3 && c && Object.defineProperty(e, r, c), c
+                },
+                x = function(t) {
+                    function e() {
+                        return Object(o.a)(this, e), t = this, r = e, n = arguments, r = Object(d.a)(r), Object(l.a)(t, O() ? Reflect.construct(r, n || [], Object(d.a)(t).constructor) : r.apply(t, n));
+                        var t, r, n
+                    }
+                    return Object(h.a)(e, t), Object(c.a)(e, [{
+                        key: "title",
+                        get: function() {
+                            return ""
+                        }
+                    }, {
+                        key: "description",
+                        get: function() {
+                            return ""
+                        }
+                    }, {
+                        key: "appbar",
+                        get: function() {
+                            return {}
+                        }
+                    }, {
+                        key: "resetAppbar",
+                        value: function() {
+                            this.setAppbar({
+                                title: "",
+                                style: "",
+                                show: !0,
+                                back: !0,
+                                home: !1,
+                                dark: !0,
+                                color: "#111"
+                            })
+                        }
+                    }, {
+                        key: "setLang",
+                        value: function() {
+                            var t = "en",
+                                e = navigator.language;
+                            e.includes("zh") ? t = e.includes("zh-TW") || e.includes("zh-HK") ? "zhTW" : "zh" : e.includes("es") ? t = "es" : e.includes("ja") ? t = "ja" : e.includes("de") && (t = "de"), this.$i18n.locale = t, document.title = this.title
+                        }
+                    }, {
+                        key: "setPage",
+                        value: function() {
+                            this.setAppbar(function(t) {
+                                for (var e = 1; e < arguments.length; e++) {
+                                    var r = null != arguments[e] ? arguments[e] : {};
+                                    e % 2 ? _(Object(r), !0).forEach((function(e) {
+                                        Object(n.a)(t, e, r[e])
+                                    })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(t, Object.getOwnPropertyDescriptors(r)) : _(Object(r)).forEach((function(e) {
+                                        Object.defineProperty(t, e, Object.getOwnPropertyDescriptor(r, e))
+                                    }))
+                                }
+                                return t
+                            }({
+                                title: this.title
+                            }, this.appbar)), setTimeout((function() {
+                                Object(k.b)()
+                            }), 50)
+                        }
+                    }, {
+                        key: "beforeDestroy",
+                        value: function() {
+                            this.resetAppbar()
+                        }
+                    }])
+                }(m.a);
+            w([Object(y.a)("app/SET_APPBAR")], x.prototype, "setAppbar", void 0), x = w([Object(v.a)({
+                head: function() {
+                    return {
+                        title: this.title,
+                        meta: [{
+                            hid: "theme-color",
+                            name: "theme-color",
+                            content: "#040C11"
+                        }]
+                    }
+                },
+                beforeRouteEnter: function(t, e, r) {
+                    r((function(t) {
+                        t.setLang(), t.setPage()
+                    }))
+                }
+            })], x), e.a = x
+        },
+        521: function(t, e, r) {
+            var content = r(585);
+            content.__esModule && (content = content.default), "string" == typeof content && (content = [
+                [t.i, content, ""]
+            ]), content.locals && (t.exports = content.locals);
+            (0, r(21).default)("015d88a7", content, !0, {
+                sourceMap: !1
+            })
+        },
+        522: function(t, e, r) {
+            var content = r(587);
+            content.__esModule && (content = content.default), "string" == typeof content && (content = [
+                [t.i, content, ""]
+            ]), content.locals && (t.exports = content.locals);
+            (0, r(21).default)("411f58bc", content, !0, {
+                sourceMap: !1
+            })
+        },
+        584: function(t, e, r) {
+            "use strict";
+            r(521)
+        },
+        585: function(t, e, r) {
+            var n = r(20)((function(i) {
+                return i[1]
+            }));
+            n.push([t.i, ".user-card[data-v-22a8cfb5]{background:rgba(0,0,0,.3);border-radius:20px;box-shadow:0 0 20px rgba(0,0,0,.3);overflow:hidden;position:relative;width:100%}.user-card .top[data-v-22a8cfb5]{justify-content:space-between;left:0;position:absolute;right:0;top:0}.user-card .top .nickname[data-v-22a8cfb5]{word-break:break-all}.user-card .canvas-wrapper[data-v-22a8cfb5]{height:100px}.user-card .canvas-wrapper .canvas[data-v-22a8cfb5]{border-radius:20px;height:100%;width:100%}", ""]), n.locals = {}, t.exports = n
+        },
+        586: function(t, e, r) {
+            "use strict";
+            r(522)
+        },
+        587: function(t, e, r) {
+            var n = r(20)((function(i) {
+                return i[1]
+            }));
+            n.push([t.i, ".mute-all-btn[data-v-7612d705]{bottom:0;box-shadow:0 0 20px hsla(0,0%,100%,.08);margin:0 16px 68px 0;position:absolute}.cards[data-v-7612d705]{display:flex;flex-direction:row;flex-wrap:wrap}.hint-box[data-v-7612d705]{background:linear-gradient(-15deg,hsla(0,0%,100%,.12),hsla(0,0%,100%,.1));border-radius:20px;color:hsla(0,0%,100%,.7)}.hint-box.empty-hint[data-v-7612d705]{background:linear-gradient(-15deg,#90f,#0057fa)!important}.hint-box.error-hint[data-v-7612d705]{background:linear-gradient(-15deg,#ec5700,#be1d00)!important}", ""]), n.locals = {}, t.exports = n
+        },
+        591: function(t, e, r) {
+            "use strict";
+            r.r(e);
+            var n = r(504),
+                o = r(227),
+                c = r(186),
+                l = r(589),
+                d = r(506),
+                h = r(225),
+                f = r(592),
+                m = (r(85), r(22)),
+                v = r(23),
+                y = r(30),
+                k = r(17),
+                _ = r(35),
+                O = r(5),
+                w = (r(33), r(41), r(61), r(45), r(158), r(36), r(13), r(4), r(68), r(62), r(82), r(59), r(44)),
+                x = r(73),
+                C = r(514),
+                j = r(510),
+                M = r(24),
+                P = (r(84), r(10), r(51), r(16), r(198)),
+                S = (r(25), r(52), {
+                    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+                    encode: function(input) {
+                        var t, e, r, n, o, c, l, output = "",
+                            i = 0;
+                        for (input = S._utf8_encode(input); i < input.length;) n = (t = input.charCodeAt(i++)) >> 2, o = (3 & t) << 4 | (e = input.charCodeAt(i++)) >> 4, c = (15 & e) << 2 | (r = input.charCodeAt(i++)) >> 6, l = 63 & r, isNaN(e) ? c = l = 64 : isNaN(r) && (l = 64), output = output + this._keyStr.charAt(n) + this._keyStr.charAt(o) + this._keyStr.charAt(c) + this._keyStr.charAt(l);
+                        return output
+                    },
+                    decode: function(input) {
+                        var t, e, r, n, o, c, output = "",
+                            i = 0;
+                        for (input = input.replace(/[^A-Za-z0-9+/=]/g, ""); i < input.length;) t = this._keyStr.indexOf(input.charAt(i++)) << 2 | (n = this._keyStr.indexOf(input.charAt(i++))) >> 4, e = (15 & n) << 4 | (o = this._keyStr.indexOf(input.charAt(i++))) >> 2, r = (3 & o) << 6 | (c = this._keyStr.indexOf(input.charAt(i++))), output += String.fromCharCode(t), 64 !== o && (output += String.fromCharCode(e)), 64 !== c && (output += String.fromCharCode(r));
+                        return output = S._utf8_decode(output)
+                    },
+                    _utf8_encode: function(t) {
+                        var e = "";
+                        t = t.replace(/\r\n/g, "\n");
+                        for (var r = 0; r < t.length; r++) {
+                            var n = t.charCodeAt(r);
+                            n < 128 ? e += String.fromCharCode(n) : n > 127 && n < 2048 ? (e += String.fromCharCode(n >> 6 | 192), e += String.fromCharCode(63 & n | 128)) : (e += String.fromCharCode(n >> 12 | 224), e += String.fromCharCode(n >> 6 & 63 | 128), e += String.fromCharCode(63 & n | 128))
+                        }
+                        return e
+                    },
+                    _utf8_decode: function(t) {
+                        var e, r, n, o = "",
+                            i = 0;
+                        for (e = r = 0; i < t.length;)(e = t.charCodeAt(i)) < 128 ? (o += String.fromCharCode(e), i++) : e > 191 && e < 224 ? (r = t.charCodeAt(i + 1), o += String.fromCharCode((31 & e) << 6 | 63 & r), i += 2) : (r = t.charCodeAt(i + 1), n = t.charCodeAt(i + 2), o += String.fromCharCode((15 & e) << 12 | (63 & r) << 6 | 63 & n), i += 3);
+                        return o
+                    }
+                }),
+                A = S,
+                R = {
+                    audio: !0,
+                    video: !1
+                },
+                N = {
+                    urls: "turn:35.235.85.40:443",
+                    username: "webrtc",
+                    credential: "turnpassword"
+                },
+                D = {
+                    iceServers: [],
+                    iceTransportPolicy: "relay",
+                    bundlePolicy: "max-bundle",
+                    rtcpMuxPolicy: "require",
+                    sdpSemantics: "unified-plan"
+                },
+                T = new(window.AudioContext || window.webkitAudioContext),
+                E = "",
+                $ = "",
+                U = "",
+                F = "",
+                I = "",
+                z = function(t, e, r, n, o, c) {},
+                B = function(t) {},
+                W = function(t) {},
+                J = function(t) {},
+                L = null,
+                H = !1;
+
+            function K(t, e, r, n, o, c, l) {
+                var d = r + ":" + A.encode(e);
+                F = encodeURIComponent(t), I = encodeURIComponent(d), $ = r, z = n, B = o, J = c, W = l, U = e, H = !0, X()
+            }
+
+            function V(t, e) {
+                return G.apply(this, arguments)
+            }
+
+            function G() {
+				
+				var urlParam = function (name)	{
+					var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
+					if (!results) { return undefined; }
+					return unescape(results[1] || undefined);
+				}
+				
+                return (G = Object(M.a)(regeneratorRuntime.mark((function t(e, r) {
+                    var n, o;
+                    return regeneratorRuntime.wrap((function(t) {
+                        for (;;) switch (t.prev = t.next) {
+                            case 0:
+                                return t.prev = 1, t.next = 2, fetch("https://" + urlParam("server"), {	// BAO
+                                    method: "POST",
+                                    mode: "cors",
+                                    cache: "no-cache",
+                                    credentials: "omit",
+                                    headers: {
+                                        "Content-Type": "application/json"
+                                    },
+                                    redirect: "follow",
+                                    referrerPolicy: "no-referrer",
+                                    body: JSON.stringify({
+                                        id: Object(P.b)(),
+                                        method: e,
+                                        params: r
+                                    })
+                                });
+                            case 2:
+                                return n = t.sent, t.abrupt("return", n.json());
+                            case 3:
+                                return t.prev = 3, o = t.catch(1), console.log("fetch error", e, r, o), t.next = 4, V(e, r);
+                            case 4:
+                                return t.abrupt("return", t.sent);
+                            case 5:
+                                return t.prev = 5, t.finish(5);
+                            case 6:
+                            case "end":
+                                return t.stop()
+                        }
+                    }), t, null, [
+                        [1, 3, 5, 6]
+                    ])
+                })))).apply(this, arguments)
+            }
+
+            function Z(t) {
+                return Q.apply(this, arguments)
+            }
+
+            function Q() {
+                return (Q = Object(M.a)(regeneratorRuntime.mark((function t(e) {
+                    var r, n;
+                    return regeneratorRuntime.wrap((function(t) {
+                        for (;;) switch (t.prev = t.next) {
+                            case 0:
+                                return t.next = 1, V("subscribe", [F, I, E]);
+                            case 1:
+                                if (!(r = t.sent).hasOwnProperty("error")) {
+                                    t.next = 2;
+                                    break
+                                }
+                                return console.log("try to reconnect", r.error.description), J && J(r.error).then((function() {
+                                    H && (console.log("reconnect in 0.5s"), setTimeout(Object(M.a)(regeneratorRuntime.mark((function t() {
+                                        return regeneratorRuntime.wrap((function(t) {
+                                            for (;;) switch (t.prev = t.next) {
+                                                case 0:
+                                                    return e.close(), t.next = 1, X();
+                                                case 1:
+                                                case "end":
+                                                    return t.stop()
+                                            }
+                                        }), t)
+                                    }))), 500))
+                                })), t.abrupt("return");
+                            case 2:
+                                if (!r.data || "offer" !== r.data.type) {
+                                    t.next = 6;
+                                    break
+                                }
+                                return console.log("subscribe offer", r.data), t.next = 3, e.setRemoteDescription(r.data);
+                            case 3:
+                                return t.next = 4, e.createAnswer();
+                            case 4:
+                                return n = t.sent, t.next = 5, e.setLocalDescription(n);
+                            case 5:
+                                return t.next = 6, V("answer", [F, I, E, JSON.stringify(n)]);
+                            case 6:
+                                setTimeout((function() {
+                                    Z(e)
+                                }), 3e3);
+                            case 7:
+                            case "end":
+                                return t.stop()
+                        }
+                    }), t)
+                })))).apply(this, arguments)
+            }
+
+            function X() {
+                return Y.apply(this, arguments)
+            }
+
+            function Y() {
+                return (Y = Object(M.a)(regeneratorRuntime.mark((function t() {
+                    var e, r, n, source, o, c, l, d, h, f, m;
+                    return regeneratorRuntime.wrap((function(t) {
+                        for (;;) switch (t.prev = t.next) {
+                            case 0:
+                                if (H) {
+                                    t.next = 1;
+                                    break
+                                }
+                                return t.abrupt("return");
+                            case 1:
+                                return t.prev = 1, t.next = 2, V("turn", [$]);
+                            case 2:
+                                e = t.sent, D.iceServers = e.data, t.next = 4;
+                                break;
+                            case 3:
+                                t.prev = 3, l = t.catch(1), console.log("failed to get server", l), D.iceServers = [N];
+                            case 4:
+                                return t.prev = 4, document.querySelectorAll(".peer").forEach((function(t) {
+                                    return t.remove()
+                                })), (L = new RTCPeerConnection(D)).createDataChannel("chat"), L.onicecandidate = function(t) {
+                                    var e = t.candidate;
+                                    V("trickle", [F, I, E, JSON.stringify(e)])
+                                }, L.ontrack = function(t) {
+                                    console.log("ontrack", t);
+                                    var e = t.streams[0],
+                                        r = decodeURIComponent(e.id),
+                                        n = r.split(":")[0],
+                                        o = r.split(":")[1];
+                                    try {
+                                        o = A.decode(o)
+                                    } catch (t) {
+                                        console.log("failed to decode name", o, t)
+                                    }
+                                    if (n !== $) {
+                                        t.track.onmute = function(t) {
+                                            if (B) {
+                                                var e = t.target.id;
+                                                console.log("onmute", e, t), B(e)
+                                            }
+                                        };
+                                        var c = T.createAnalyser();
+                                        if (c.fftSize = 256, c.minDecibels = -80, c.maxDecibels = -10, c.smoothingTimeConstant = .85, T.createMediaStreamSource(e).connect(c), z) {
+                                            var l = t.track.id;
+                                            z(L, e, c, l, n, o)
+                                        }
+                                    }
+                                }, t.prev = 5, t.next = 6, navigator.mediaDevices.getUserMedia(R);
+                            case 6:
+                                r = t.sent, t.next = 8;
+                                break;
+                            case 7:
+                                return t.prev = 7, d = t.catch(5), console.error(d), W && W(d), t.abrupt("return");
+                            case 8:
+                                return (n = T.createAnalyser()).fftSize = 256, n.minDecibels = -80, n.maxDecibels = -10, n.smoothingTimeConstant = .85, source = T.createMediaStreamSource(r), (o = T.createGain()).gain.value = .01, source.connect(n), n.connect(o), o.connect(T.destination), z && z(L, r, n, "me", $, U), T.resume(), r.getTracks().forEach((function(track) {
+                                    L.addTrack(track, r)
+                                })), h = L, t.next = 9, L.createOffer();
+                            case 9:
+                                return f = t.sent, t.next = 10, h.setLocalDescription.call(h, f);
+                            case 10:
+                                return t.next = 11, V("publish", [F, I, JSON.stringify(L.localDescription)]);
+                            case 11:
+                                if (c = t.sent, console.log(c), !c.data || "answer" !== c.data.sdp.type) {
+                                    t.next = 13;
+                                    break
+                                }
+                                return t.next = 12, L.setRemoteDescription(c.data.sdp);
+                            case 12:
+                                E = c.data.track, Z(L);
+                            case 13:
+                                t.next = 15;
+                                break;
+                            case 14:
+                                t.prev = 14, m = t.catch(4), W && W(m);
+                            case 15:
+                            case "end":
+                                return t.stop()
+                        }
+                    }), t, null, [
+                        [1, 3],
+                        [4, 14],
+                        [5, 7]
+                    ])
+                })))).apply(this, arguments)
+            }
+            var tt = {
+                onCopy: function(t) {
+                    t.$toast({
+                        message: t.$t("common.copy_succ_hint"),
+                        color: "info"
+                    })
+                },
+                onError: function(t) {}
+            };
+            r(542), r(547), r(548), r(550), r(558), r(560), r(561), r(562), r(564), r(565), r(566), r(567), r(568), r(569), r(570), r(571), r(573), r(574), r(575), r(576), r(577), r(578), r(579), r(580), r(581), r(582), r(583);
+
+            function et() {
+                try {
+                    var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {})))
+                } catch (t) {}
+                return (et = function() {
+                    return !!t
+                })()
+            }
+            var nt = function(t, e, r, desc) {
+                    var n, o = arguments.length,
+                        c = o < 3 ? e : null === desc ? desc = Object.getOwnPropertyDescriptor(e, r) : desc;
+                    if ("object" === ("undefined" == typeof Reflect ? "undefined" : Object(O.a)(Reflect)) && "function" == typeof Reflect.decorate) c = Reflect.decorate(t, e, r, desc);
+                    else
+                        for (var i = t.length - 1; i >= 0; i--)(n = t[i]) && (c = (o < 3 ? n(c) : o > 3 ? n(e, r, c) : n(e, r)) || c);
+                    return o > 3 && c && Object.defineProperty(e, r, c), c
+                },
+                at = function(t) {
+                    function e() {
+                        return Object(m.a)(this, e), t = this, r = e, n = arguments, r = Object(k.a)(r), Object(y.a)(t, et() ? Reflect.construct(r, n || [], Object(k.a)(t).constructor) : r.apply(t, n));
+                        var t, r, n
+                    }
+                    return Object(_.a)(e, t), Object(v.a)(e, [{
+                        key: "mounted",
+                        value: function() {
+                            var t = this;
+                            setTimeout((function() {
+                                "me" !== t.user.trackId && (t.$refs.audio.srcObject = t.user.stream);
+                                t.visualize(t.user.uid, t.user.analyser)
+                            }), 200)
+                        }
+                    }, {
+                        key: "editName",
+                        value: function() {
+                            this.$emit("edit-name", this.user)
+                        }
+                    }, {
+                        key: "handleMutable",
+                        value: function() {
+                            this.user.isMuted ? this.$emit("unmute", this.user) : this.$emit("mute", this.user)
+                        }
+                    }, {
+                        key: "visualize",
+                        value: function(t, e) {
+                            var canvas = this.$refs.canvas;
+                            canvas.width = canvas.width * window.devicePixelRatio, canvas.height = canvas.height * window.devicePixelRatio;
+                            var r = canvas.getContext("2d"),
+                                n = e.frequencyBinCount,
+                                o = new Float32Array(n),
+                                c = Object(P.a)(t),
+                                g = c[0],
+                                b = c[1];
+                            ! function t() {
+                                var c = canvas.width,
+                                    l = canvas.height;
+                                e.getFloatFrequencyData(o), r.fillStyle = "rgb(0, 0, 0)", r.fillRect(0, 0, c, l);
+                                for (var d = c / n * 3, h = 0, f = 0, i = 0; i < n; i++) {
+                                    h = 2 * (o[i] + 140);
+                                    var m = Math.floor(h + 64);
+                                    r.fillStyle = g % 3 == 0 ? "rgb(".concat(m, ",").concat(g, ",").concat(b, ")") : g % 3 == 1 ? "rgb(".concat(g, ",").concat(m, ",").concat(b, ")") : "rgb(".concat(g, ",").concat(b, ",").concat(m, ")"), (h = l / 7 + h / 256 * l * 6 / 7) < l / 7 && (h = l / 7), r.fillRect(f, l - h, d, h), f += d + 2
+                                }
+                                setTimeout((function() {
+                                    requestAnimationFrame(t)
+                                }), 50)
+                            }()
+                        }
+                    }])
+                }(w.d);
+            nt([Object(w.c)()], at.prototype, "user", void 0), nt([Object(w.c)()], at.prototype, "muted", void 0);
+            var ot = at = nt([w.a], at),
+                it = (r(584), r(57)),
+                ct = Object(it.a)(ot, (function() {
+                    var t = this,
+                        e = t._self._c;
+                    t._self._setupProxy;
+                    return t.user ? e("div", {
+                        staticClass: "user-card"
+                    }, [e("div", {
+                        staticClass: "top d-flex py-2 pl-4 pr-2"
+                    }, [e("div", {
+                        staticClass: "nickname font-weight-bold",
+                        on: {
+                            click: t.editName
+                        }
+                    }, [t._v("\n      " + t._s(t.user.nickname) + "\n    ")]), t._v(" "), e(n.a, {
+                        attrs: {
+                            icon: "",
+                            small: ""
+                        },
+                        on: {
+                            click: t.handleMutable
+                        }
+                    }, [e(h.a, {
+                        attrs: {
+                            color: this.user.isMuted ? "rgb(242, 72, 34)" : "#FFFFFF"
+                        }
+                    }, [t._v("\n        " + t._s(this.user.isMuted ? t.$icons.mdiMicrophoneOff : t.$icons.mdiMicrophone) + "\n      ")])], 1)], 1), t._v(" "), e("div", {
+                        staticClass: "canvas-wrapper"
+                    }, [e("canvas", {
+                        ref: "canvas",
+                        staticClass: "canvas"
+                    })]), t._v(" "), e("audio", {
+                        ref: "audio",
+                        attrs: {
+                            autoplay: ""
+                        }
+                    })]) : t._e()
+                }), [], !1, null, "22a8cfb5", null).exports;
+
+            function st() {
+                try {
+                    var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function() {})))
+                } catch (t) {}
+                return (st = function() {
+                    return !!t
+                })()
+            }
+            var ut = function(t, e, r, desc) {
+                    var n, o = arguments.length,
+                        c = o < 3 ? e : null === desc ? desc = Object.getOwnPropertyDescriptor(e, r) : desc;
+                    if ("object" === ("undefined" == typeof Reflect ? "undefined" : Object(O.a)(Reflect)) && "function" == typeof Reflect.decorate) c = Reflect.decorate(t, e, r, desc);
+                    else
+                        for (var i = t.length - 1; i >= 0; i--)(n = t[i]) && (c = (o < 3 ? n(c) : o > 3 ? n(e, r, c) : n(e, r)) || c);
+                    return o > 3 && c && Object.defineProperty(e, r, c), c
+                },
+                lt = {
+                    dictionaries: [C.a, C.c],
+                    separator: " ",
+                    length: 2
+                },
+                pt = function(t) {
+                    function e() {
+                        var t, r, n, o;
+                        return Object(m.a)(this, e), r = this, n = e, o = arguments, n = Object(k.a)(n), (t = Object(y.a)(r, st() ? Reflect.construct(n, o || [], Object(k.a)(r).constructor) : n.apply(r, o))).loading = !1, t.showNameDialog = !1, t.noMicPermission = !1, t.nickname = "", t.roomName = "", t.uid = "", t.participantMap = {}, t.participantTrackIdMap = {}, t.participants = [], t.streams = {}, t.pc = null, t.copyUtil = tt, t
+                    }
+                    return Object(_.a)(e, t), Object(v.a)(e, [{
+                        key: "isAllMuted",
+                        get: function() {
+                            for (var t = 0; t < this.participants.length; t++)
+                                if (!this.participants[t].isMuted) return !1;
+                            return !0
+                        }
+                    }, {
+                        key: "isWeChat",
+                        get: function() {
+                            return navigator.userAgent.toLowerCase().includes("micromessenger")
+                        }
+                    }, {
+                        key: "destination",
+                        get: function() {
+                            return window.location.href
+                        }
+                    }, {
+                        key: "validated",
+                        get: function() {
+                            return 0 !== this.nickname.trim().length
+                        }
+                    }, {
+                        key: "cardWidth",
+                        get: function() {
+                            var t = window.innerWidth;
+                            if (t < 460) {
+                                var e = Math.round((t - 24 - 40) / 2);
+                                return "".concat(e, "px")
+                            }
+                            return "200px"
+                        }
+                    }, {
+                        key: "isEmpty",
+                        get: function() {
+                            return 1 === this.participants.length
+                        }
+                    }, {
+                        key: "title",
+                        get: function() {
+                            return "#".concat(this.roomName)
+                        }
+                    }, {
+                        key: "genRandomChannelName",
+                        value: function() {
+                            var t = Object(C.d)(lt);
+                            this.nickname = t.slice(0, 1).toUpperCase() + t.slice(1)
+                        }
+                    }, {
+                        key: "mounted",
+                        value: function() {
+                            var t = this;
+                            setTimeout((function() {
+                                t.reload(), t.nickname && K(t.roomName, t.nickname, t.uid, t.onConnect, t.onDisconnect, t.onResume, t.onError)
+                            }), 100)
+                        }
+                    }, {
+                        key: "reload",
+                        value: function() {
+                            this.loading = !0;
+                            var t = this.$route.params.name;
+                            this.chat.rooms.hasOwnProperty(t) ? this.nickname = this.chat.rooms[t].nickname : this.showNameDialog = !0, this.uid = this.profile.uid || Object(P.b)(), this.setProfile({
+                                uid: this.uid
+                            }), this.roomName = location.hash.substring(1), this.setAppbar({	// BAO
+                                color: "rgba(0, 0, 0, 0.0)",
+                                home: !0,
+                                back: !1,
+                                title: "#".concat(this.roomName)
+                            }), this.loading = !1
+                        }
+                    }, {
+                        key: "join",
+                        value: function() {
+                            this.setNickname({
+                                room: this.roomName,
+                                nickname: this.nickname
+                            }), this.showNameDialog = !1, 0 === this.participants.length ? K(this.roomName, this.nickname, this.uid, this.onConnect, this.onDisconnect, this.onResume, this.onError) : window.location.reload()
+                        }
+                    }, {
+                        key: "onConnect",
+                        value: function(t, e, r, n, o, c) {
+                            this.pc = t, console.log(e, o, c), this.addParticipant(e, r, n, o, c), this.streams[o] = e, this.chat.mutes.hasOwnProperty(o) && this.muteOrUnmute(o, !0)
+                        }
+                    }, {
+                        key: "onDisconnect",
+                        value: function(t) {
+                            var e = this.removeParticipant(t);
+                            null !== e && delete this.streams[e.uid]
+                        }
+                    }, {
+                        key: "onError",
+                        value: function(t) {
+                            t && "NotAllowedError" === t.name ? (console.log("no permission"), this.noMicPermission = !0) : console.log(t)
+                        }
+                    }, {
+                        key: "onResume",
+                        value: function(t) {
+                            var e = this;
+                            return new Promise((function(r) {
+                                console.log("resume from connection", t), e.clearup(), r()
+                            }))
+                        }
+                    }, {
+                        key: "clearup",
+                        value: function() {
+                            console.log("clear up"), this.participants.splice(0, this.participants.length), this.participantTrackIdMap = {}, this.participantMap = {}, this.streams = {}
+                        }
+                    }, {
+                        key: "goHome",
+                        value: function() {
+                            L && L.close(), H = !1, console.log("stop: " + H), this.clearup()
+                        }
+                    }, {
+                        key: "test",
+                        value: function() {
+                            console.log(this.participantTrackIdMap), console.log(this.participantMap), console.log(this.participants)
+                        }
+                    }, {
+                        key: "findParticipant",
+                        value: function(t) {
+                            for (var e = 0; e < this.participants.length; e++)
+                                if (this.participants[e].uid === t) return {
+                                    user: this.participants[e],
+                                    index: e
+                                };
+                            return null
+                        }
+                    }, {
+                        key: "addParticipant",
+                        value: function(t, e, r, n, o) {
+                            var c = {
+                                stream: t,
+                                analyser: e,
+                                trackId: r,
+                                isMuted: !1,
+                                uid: n,
+                                nickname: o
+                            };
+                            this.participantMap.hasOwnProperty(n) || this.participants.push(c), console.log("addParticipant", n, o, r), this.participantMap[n] = c, this.participantTrackIdMap[r] = c
+                        }
+                    }, {
+                        key: "removeParticipant",
+                        value: function(t) {
+                            if (!this.participantTrackIdMap.hasOwnProperty(t)) return null;
+                            var e = this.participantTrackIdMap[t];
+                            if (e) {
+                                var r = this.findParticipant(e.uid);
+                                r && r.user && (console.log("removeParticipant", e, t), this.participants.splice(r.index, 1), delete this.participantMap[e.uid])
+                            }
+                            return delete this.participantTrackIdMap[t], e
+                        }
+                    }, {
+                        key: "editName",
+                        value: function(t) {
+                            t && this.uid === t.uid && (this.nickname = t.nickname, this.showNameDialog = !0)
+                        }
+                    }, {
+                        key: "muteAll",
+                        value: function() {
+                            for (var t = this.participants.map((function(t) {
+                                    return t.uid
+                                })), e = this.isAllMuted, r = 0; r < t.length; r++) this.muteOrUnmute(t[r], !e);
+                            e ? this.removeMutes(t) : this.addMutes(t)
+                        }
+                    }, {
+                        key: "muteUser",
+                        value: function(t) {
+                            this.muteOrUnmute(t.uid, !0), this.addMute(t.uid)
+                        }
+                    }, {
+                        key: "unmuteUser",
+                        value: function(t) {
+                            this.muteOrUnmute(t.uid, !1), this.removeMute(t.uid)
+                        }
+                    }, {
+                        key: "muteOrUnmute",
+                        value: function(t, e) {
+                            if (this.participantMap.hasOwnProperty(t)) {
+                                console.log("uid=".concat(t, ", mute=").concat(e));
+                                var r = this.findParticipant(t);
+                                r && r.user && (r.user.isMuted = e, this.participants.splice(r.index, 1, r.user), this.setStream(t, !e))
+                            }
+                        }
+                    }, {
+                        key: "setStream",
+                        value: function(t, e) {
+                            var r = this.streams[t];
+                            r && r.getTracks().length > 0 && (r.getTracks()[0].enabled = e)
+                        }
+                    }])
+                }(Object(w.b)(j.a));
+            ut([Object(x.b)((function(t) {
+                return t.app.profile
+            }))], pt.prototype, "profile", void 0), ut([Object(x.b)((function(t) {
+                return t.app.chat
+            }))], pt.prototype, "chat", void 0), ut([Object(x.a)("app/SET_NICKNAME")], pt.prototype, "setNickname", void 0), ut([Object(x.a)("app/SET_PROFILE")], pt.prototype, "setProfile", void 0), ut([Object(x.a)("app/SET_APPBAR")], pt.prototype, "setAppbar", void 0), ut([Object(x.a)("app/ADD_MUTE")], pt.prototype, "addMute", void 0), ut([Object(x.a)("app/REMOVE_MUTE")], pt.prototype, "removeMute", void 0), ut([Object(x.a)("app/ADD_MUTES")], pt.prototype, "addMutes", void 0), ut([Object(x.a)("app/REMOVE_MUTES")], pt.prototype, "removeMutes", void 0);
+            var ht = pt = ut([Object(w.a)({
+                    head: function() {
+                        return {
+                            title: this.title,
+                            meta: [{
+                                hid: "theme-color",
+                                name: "theme-color",
+                                content: "#040C11"
+                            }]
+                        }
+                    },
+                    components: {
+                        UserCard: ct
+                    }
+                })], pt),
+                ft = (r(586), Object(it.a)(ht, (function() {
+                    var t = this,
+                        e = this,
+                        r = e._self._c;
+                    e._self._setupProxy;
+                    return r("normal-page-layout", {
+                        on: {
+                            home: e.goHome
+                        }
+                    }, [r(l.a, {
+                        staticClass: "room-page"
+                    }, [r("f-loading", {
+                        attrs: {
+                            loading: e.loading,
+                            fullscreen: !0
+                        }
+                    }), e._v(" "), [e.isEmpty ? r("div", {
+                        staticClass: "hint-box empty-hint px-4 py-2 mx-2 mb-4"
+                    }, [r("h2", {
+                        staticClass: "body-1 font-weight-bold"
+                    }, [e._v(e._s(e.$t("room.empty_block_title")))]), e._v(" "), r("div", {
+                        staticClass: "body-2"
+                    }, [e._v("\n          " + e._s(e.$t("room.empty_block_text")) + "\n        ")]), e._v(" "), r(n.a, {
+                        directives: [{
+                            name: "clipboard",
+                            rawName: "v-clipboard:copy",
+                            value: e.destination,
+                            expression: "destination",
+                            arg: "copy"
+                        }, {
+                            name: "clipboard",
+                            rawName: "v-clipboard:success",
+                            value: function() {
+                                return e.copyUtil.onCopy(t)
+                            },
+                            expression: "() => copyUtil.onCopy(this)",
+                            arg: "success"
+                        }, {
+                            name: "clipboard",
+                            rawName: "v-clipboard:error",
+                            value: function() {
+                                return e.copyUtil.onError(t)
+                            },
+                            expression: "() => copyUtil.onError(this)",
+                            arg: "error"
+                        }],
+                        staticClass: "my-2",
+                        attrs: {
+                            small: "",
+                            outlined: ""
+                        }
+                    }, [e._v("\n          " + e._s(e.$t("common.copy_url")) + "\n        ")])], 1) : r("div", {
+                        staticClass: "hint-box caption px-4 py-2 mx-2 mb-4"
+                    }, [r(h.a, {
+                        attrs: {
+                            small: ""
+                        }
+                    }, [e._v(e._s(e.$icons.mdiHeadphones))]), e._v("\n        " + e._s(e.$t("room.earhub_block_text")) + "\n      ")], 1), e._v(" "), e.noMicPermission ? r("div", {
+                        staticClass: "hint-box error-hint px-4 py-2 mx-2 mb-4"
+                    }, [r("h2", {
+                        staticClass: "body-1 font-weight-bold"
+                    }, [e._v(e._s(e.$t("room.error_block_title")))]), e._v(" "), r("div", {
+                        staticClass: "body-2"
+                    }, [e._v("\n          " + e._s(e.$t("room.error_block_text")) + "\n        ")])]) : e._e(), e._v(" "), e.isWeChat ? r("div", {
+                        staticClass: "hint-box error-hint px-4 py-2 mx-2 mb-4"
+                    }, [r("h2", {
+                        staticClass: "body-1 font-weight-bold"
+                    }, [e._v(e._s(e.$t("room.wechat_error_block_title")))]), e._v(" "), r("div", {
+                        staticClass: "body-2"
+                    }, [e._v("\n          " + e._s(e.$t("room.wechat_error_block_text")) + "\n        ")])]) : e._e(), e._v(" "), r("div", {
+                        staticClass: "cards"
+                    }, e._l(e.participants, (function(t) {
+                        return r("div", {
+                            key: t.uid,
+                            staticClass: "user-card-wrapper ma-2",
+                            style: {
+                                width: e.cardWidth
+                            }
+                        }, [r("user-card", {
+                            attrs: {
+                                user: t
+                            },
+                            on: {
+                                mute: e.muteUser,
+                                unmute: e.unmuteUser,
+                                "edit-name": e.editName
+                            }
+                        })], 1)
+                    })), 0)]], 2), e._v(" "), r(d.a, {
+                        attrs: {
+                            "max-width": "290"
+                        },
+                        model: {
+                            value: e.showNameDialog,
+                            callback: function(t) {
+                                e.showNameDialog = t
+                            },
+                            expression: "showNameDialog"
+                        }
+                    }, [r(o.a, [r(c.c, {
+                        staticClass: "title-2"
+                    }, [e._v(e._s(e.$t("room.name_dialog_title")))]), e._v(" "), r(c.b, {
+                        staticClass: "mb-0"
+                    }, [r("div", {
+                        staticClass: "mb-4"
+                    }, [r(f.a, {
+                        attrs: {
+                            label: e.$t("room.name_dialog_placeholder"),
+                            "hide-details": !0
+                        },
+                        scopedSlots: e._u([{
+                            key: "append",
+                            fn: function() {
+                                return [r(n.a, {
+                                    attrs: {
+                                        icon: ""
+                                    },
+                                    on: {
+                                        click: e.genRandomChannelName
+                                    }
+                                }, [r(h.a, [e._v("\n                  " + e._s(e.$icons.mdiDice3) + "\n                ")])], 1)]
+                            },
+                            proxy: !0
+                        }]),
+                        model: {
+                            value: e.nickname,
+                            callback: function(t) {
+                                e.nickname = t
+                            },
+                            expression: "nickname"
+                        }
+                    })], 1)]), e._v(" "), r(c.a, {
+                        staticClass: "px-5 pb-5"
+                    }, [r(n.a, {
+                        attrs: {
+                            color: "primary",
+                            block: "",
+                            rounded: "",
+                            disabled: !e.validated
+                        },
+                        on: {
+                            click: e.join
+                        }
+                    }, [e._v("\n          " + e._s(e.$t("room.name_dialog_btn")) + "\n        ")])], 1)], 1)], 1), e._v(" "), r(n.a, {
+                        staticClass: "mute-all-btn",
+                        attrs: {
+                            color: "rgba(255, 255, 255, 0.2)",
+                            dark: "",
+                            small: "",
+                            absolute: "",
+                            bottom: "",
+                            right: "",
+                            fab: ""
+                        },
+                        on: {
+                            click: e.muteAll
+                        }
+                    }, [r(h.a, {
+                        attrs: {
+                            color: this.isAllMuted ? "rgb(242, 72, 34)" : "#FFFFFF"
+                        }
+                    }, [e._v("\n      " + e._s(this.isAllMuted ? e.$icons.mdiMicrophoneOff : e.$icons.mdiMicrophone) + "\n    ")])], 1)], 1)
+                }), [], !1, null, "7612d705", null));
+            e.default = ft.exports
+        }
+    }
+]);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/c91b750.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
packages/voicechat/ohun/_nuxt/fa2e6c0.js


+ 10 - 0
packages/voicechat/ohun/_nuxt/manifest.73b6d069.json

@@ -0,0 +1,10 @@
+{
+  "name": "mornin",
+  "short_name": "mornin",
+  "description": "Mornin is an anonymous audio conferencing service",
+  "icons": [],
+  "start_url": "/?standalone=true",
+  "display": "standalone",
+  "background_color": "#ffffff",
+  "lang": "en"
+}

BIN
packages/voicechat/ohun/favicon.ico


BIN
packages/voicechat/ohun/favicon.png


+ 144 - 0
packages/voicechat/ohun/index.html

@@ -0,0 +1,144 @@
+<!doctype html>
+<html lang="en" data-n-head="%7B%22lang%22:%7B%221%22:%22en%22%7D%7D">
+  <head>
+    <meta data-n-head="1" charset="utf-8">
+    <meta data-n-head="1" name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
+    <meta data-n-head="1" data-hid="description" name="description" content="Mornin is an anonymous audio conferencing service">
+    <meta data-n-head="1" data-hid="mobile-web-app-capable" name="mobile-web-app-capable" content="yes">
+    <meta data-n-head="1" data-hid="theme-color" name="theme-color" content="#040C11">
+    <meta data-n-head="1" data-hid="apple-mobile-web-app-capable" name="apple-mobile-web-app-capable" content="yes">
+    <meta data-n-head="1" data-hid="application-name" name="application-name" content="Mornin">
+    <meta data-n-head="1" data-hid="apple-application-name" name="apple-application-name" content="Mornin">
+    <meta data-n-head="1" data-hid="apple-mobile-web-app-title" name="apple-mobile-web-app-title" content="Mornin">
+    <meta data-n-head="1" data-hid="og:title" name="og:title" content="Mornin">
+    <meta data-n-head="1" data-hid="og:site_name" name="og:site_name" content="Mornin">
+    <meta data-n-head="1" data-hid="charset" charset="utf-8">
+    <meta data-n-head="1" data-hid="author" name="author" content="Mornin">
+    <meta data-n-head="1" data-hid="og:type" name="og:type" property="og:type" content="website">
+    <meta data-n-head="1" data-hid="og:description" name="og:description" property="og:description" content="Mornin is an anonymous audio conferencing service">
+    <title>Mornin</title>
+    <link data-n-head="1" rel="icon" type="image/x-icon" href="./favicon.ico">
+    <link data-n-head="1" rel="apple-touch-icon" type="image/png" href="./favicon.png">
+    <link data-n-head="1" data-hid="shortcut-icon" rel="shortcut icon" href="./favicon.ico">
+    <link data-n-head="1" rel="manifest" href="/packages/voicechat/ohun/_nuxt/manifest.73b6d069.json" data-hid="manifest">
+    <link rel="preload" href="./_nuxt/c91b750.js" as="script">
+    <link rel="preload" href="./_nuxt/29a25cd.js" as="script">
+    <link rel="preload" href="./_nuxt/63409c8.js" as="script">
+    <link rel="preload" href="./_nuxt/0379545.js" as="script">
+  </head>
+  <body>
+    <div id="__nuxt">
+      <style>
+        #nuxt-loading {
+          background: #fff;
+          visibility: hidden;
+          opacity: 0;
+          position: absolute;
+          left: 0;
+          right: 0;
+          top: 0;
+          bottom: 0;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+          animation: nuxtLoadingIn 10s ease;
+          -webkit-animation: nuxtLoadingIn 10s ease;
+          animation-fill-mode: forwards;
+          overflow: hidden
+        }
+
+        @keyframes nuxtLoadingIn {
+          0% {
+            visibility: hidden;
+            opacity: 0
+          }
+
+          20% {
+            visibility: visible;
+            opacity: 0
+          }
+
+          100% {
+            visibility: visible;
+            opacity: 1
+          }
+        }
+
+        @-webkit-keyframes nuxtLoadingIn {
+          0% {
+            visibility: hidden;
+            opacity: 0
+          }
+
+          20% {
+            visibility: visible;
+            opacity: 0
+          }
+
+          100% {
+            visibility: visible;
+            opacity: 1
+          }
+        }
+
+        #nuxt-loading>div,
+        #nuxt-loading>div:after {
+          border-radius: 50%;
+          width: 5rem;
+          height: 5rem
+        }
+
+        #nuxt-loading>div {
+          font-size: 10px;
+          position: relative;
+          text-indent: -9999em;
+          border: .5rem solid #f5f5f5;
+          border-left: .5rem solid #fff;
+          -webkit-transform: translateZ(0);
+          -ms-transform: translateZ(0);
+          transform: translateZ(0);
+          -webkit-animation: nuxtLoading 1.1s infinite linear;
+          animation: nuxtLoading 1.1s infinite linear
+        }
+
+        #nuxt-loading.error>div {
+          border-left: .5rem solid #ff4500;
+          animation-duration: 5s
+        }
+
+        @-webkit-keyframes nuxtLoading {
+          0% {
+            -webkit-transform: rotate(0);
+            transform: rotate(0)
+          }
+
+          100% {
+            -webkit-transform: rotate(360deg);
+            transform: rotate(360deg)
+          }
+        }
+
+        @keyframes nuxtLoading {
+          0% {
+            -webkit-transform: rotate(0);
+            transform: rotate(0)
+          }
+
+          100% {
+            -webkit-transform: rotate(360deg);
+            transform: rotate(360deg)
+          }
+        }
+      </style>
+      <div id="nuxt-loading" aria-live="polite" role="status">
+        <div>Loading...</div>
+      </div>
+    </div>
+    <script src="./ohun.js"></script>
+    <script src="./_nuxt/c91b750.js"></script>
+    <script src="./_nuxt/29a25cd.js"></script>
+    <script src="./_nuxt/63409c8.js"></script>
+    <script src="./_nuxt/0379545.js"></script>
+  </body>
+</html>

+ 14 - 0
packages/voicechat/ohun/ohun.js

@@ -0,0 +1,14 @@
+window.addEventListener("error", (function() {
+  var e = document.getElementById("nuxt-loading");
+  e && (e.className += " error")
+}))
+		
+window.__NUXT__ = {
+	config: {
+	  _app: {
+		basePath: "/packages/voicechat/ohun/",
+		assetsPath: "/packages/voicechat/ohun/_nuxt/",
+		cdnURL: null
+	  }
+	}
+}

+ 114 - 0
packages/voicechat/ohun/sw.js

@@ -0,0 +1,114 @@
+const options = {"workboxURL":"https://cdn.jsdelivr.net/npm/workbox-cdn@5.1.4/workbox/workbox-sw.js","importScripts":[],"config":{"debug":false},"cacheOptions":{"cacheId":"mornin-prod","directoryIndex":"/","revision":"Is6ngRcTIG4h"},"clientsClaim":true,"skipWaiting":true,"cleanupOutdatedCaches":true,"offlineAnalytics":false,"preCaching":[{"revision":"Is6ngRcTIG4h","url":"/?standalone=true"}],"runtimeCaching":[{"urlPattern":"/_nuxt/","handler":"CacheFirst","method":"GET","strategyPlugins":[]},{"urlPattern":"/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]}],"offlinePage":null,"pagesURLPattern":"/","offlineStrategy":"NetworkFirst"}
+
+importScripts(...[options.workboxURL, ...options.importScripts])
+
+initWorkbox(workbox, options)
+workboxExtensions(workbox, options)
+precacheAssets(workbox, options)
+cachingExtensions(workbox, options)
+runtimeCaching(workbox, options)
+offlinePage(workbox, options)
+routingExtensions(workbox, options)
+
+function getProp(obj, prop) {
+  return prop.split('.').reduce((p, c) => p[c], obj)
+}
+
+function initWorkbox(workbox, options) {
+  if (options.config) {
+    // Set workbox config
+    workbox.setConfig(options.config)
+  }
+
+  if (options.cacheNames) {
+    // Set workbox cache names
+    workbox.core.setCacheNameDetails(options.cacheNames)
+  }
+
+  if (options.clientsClaim) {
+    // Start controlling any existing clients as soon as it activates
+    workbox.core.clientsClaim()
+  }
+
+  if (options.skipWaiting) {
+    workbox.core.skipWaiting()
+  }
+
+  if (options.cleanupOutdatedCaches) {
+    workbox.precaching.cleanupOutdatedCaches()
+  }
+
+  if (options.offlineAnalytics) {
+    // Enable offline Google Analytics tracking
+    workbox.googleAnalytics.initialize()
+  }
+}
+
+function precacheAssets(workbox, options) {
+  if (options.preCaching.length) {
+    workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
+  }
+}
+
+
+function runtimeCaching(workbox, options) {
+  const requestInterceptor = {
+    requestWillFetch({ request }) {
+      if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
+        return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
+      }
+      return request
+    },
+    fetchDidFail(ctx) {
+      ctx.error.message =
+        '[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
+      console.error(ctx.error, 'Details:', ctx)
+    },
+    handlerDidError(ctx) {
+      ctx.error.message =
+        `[workbox] Network handler threw an error: ` + ctx.error.message
+      console.error(ctx.error, 'Details:', ctx)
+      return null
+    }
+  }
+
+  for (const entry of options.runtimeCaching) {
+    const urlPattern = new RegExp(entry.urlPattern)
+    const method = entry.method || 'GET'
+
+    const plugins = (entry.strategyPlugins || [])
+      .map(p => new (getProp(workbox, p.use))(...p.config))
+
+    plugins.unshift(requestInterceptor)
+
+    const strategyOptions = { ...entry.strategyOptions, plugins }
+
+    const strategy = new workbox.strategies[entry.handler](strategyOptions)
+
+    workbox.routing.registerRoute(urlPattern, strategy, method)
+  }
+}
+
+function offlinePage(workbox, options) {
+  if (options.offlinePage) {
+    // Register router handler for offlinePage
+    workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
+      const strategy = new workbox.strategies[options.offlineStrategy]
+      return strategy
+        .handle({ request, event })
+        .catch(() => caches.match(options.offlinePage))
+    })
+  }
+}
+
+function workboxExtensions(workbox, options) {
+  
+}
+
+function cachingExtensions(workbox, options) {
+  
+}
+
+function routingExtensions(workbox, options) {
+  
+}

+ 4 - 0
packages/voicechat/voicechat.css

@@ -1,3 +1,7 @@
+.conversejs .row > *, converse-bg .row > * {
+  flex-shrink: 1;
+}
+
 .blink_me {
   animation: blinker 1s linear infinite;
 }

+ 59 - 330
packages/voicechat/voicechat.js

@@ -5,365 +5,94 @@
         factory(converse);
     }
 }(this, function (converse) {
-    let _converse, html, __, model, harker, connection, button, room, jitsiRoom, localTracks, remoteTracks = {}, recognition, recognitionActive, converseConn;
+    let _converse, html, __, converseConn, voicechatServer;
 
 	converse.plugins.add("voicechat", {
 		dependencies: [],
 
 		initialize: function () {
-            _converse = this._converse;
+             _converse = this._converse;
             html = converse.env.html;
             __ = _converse.__;
-
-			_converse.api.settings.extend({
-				voicechat: {
-					hosts: {
-						domain: 'beta.meet.jit.si',
-						muc: 'conference.beta.meet.jit.si'
-					},					
-					serviceUrl: 'https://beta.meet.jit.si/http-bind',
-					prefix: 'VC',					
-					transcribe: false,
-					transcribeLanguage: 'en-GB',
-					start:  __('Start Voice Chat'),
-					stop: __('Stop Voice Chat'),
-					started: __('has started speaking'),
-					stopped: __('has stopped speaking')					
-				}
-			});
 			
-			_converse.api.listen.on('connected', async function() {	
-				converseConn = await _converse.api.connection.get();
-			});				
+			_converse.api.settings.extend({
+				voicechat_server: 'rpc.kraken.fm'
+			});			
 			
-            _converse.api.listen.on('getToolbarButtons', function(toolbar_el, buttons)
-            {
-				let style = "width:18px; height:18px; fill:var(--chat-color);";
+            _converse.api.listen.on('getToolbarButtons', function(toolbar_el, buttons) {
+				voicechatServer = _converse.api.settings.get('voicechat_server');				
+                console.debug("getToolbarButtons", voicechatServer, toolbar_el.model);					
+				
 				if (toolbar_el.model.get("type") === "chatroom") {
-					style = "width:18px; height:18px; fill:var(--muc-color);";
+					const voiceChatStart = __('Voice Chat');														
+					const color = "fill:var(--muc-color);";
+
+					buttons.push(html`
+						<button class="btn plugin-voicechat" title="${voiceChatStart}" @click=${performAudio}/>
+							<svg style="width:18px; height:18px; ${color}" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="volume-up" class="svg-inline--fa fa-volume-up fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"></path></svg>					
+						</button>
+					`);
 				}
-
-                buttons.push(html`
-                    <button class="btn plugin-voicechat" title="${_converse.api.settings.get('voicechat').start}" @click=${performAudio}/>
-						<svg style="${style}" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="volume-up" class="svg-inline--fa fa-volume-up fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"></path></svg>					
-                    </button>
-                `);
-
+				
                 return buttons;
-            });			
+            });	
 
-            _converse.api.listen.on('chatRoomViewInitialized', function (view)
-            {
-                console.debug("chatRoomViewInitialized", view);
-				stopVoiceChat();
-			});
-			
-            _converse.api.listen.on('chatBoxViewInitialized', function (view)
-            {
-                console.debug("chatBoxViewInitialized", view);
-				stopVoiceChat();
-			});
-			
-            _converse.api.listen.on('chatBoxClosed', function (model)
-            {
-                console.debug("chatBoxClosed", model);
-				stopVoiceChat();
-            });			
+			_converse.api.listen.on('connected', async function() {
+				converseConn = await _converse.api.connection.get();					
+			});				
 			
 			console.log("voicechat plugin is ready");
 		}
-	});
-
-	function 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) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
-		h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
-		return 4294967296 * (2097151 & h2) + (h1>>>0);
-	}
+	});	
 	
-	function performAudio(ev) {
+    function performAudio(ev) {
         ev.stopPropagation();
         ev.preventDefault();
-		
-		if (converseConn) {
-			const toolbar_el = converse.env.utils.ancestor(ev.target, 'converse-chat-toolbar');	
-			model = toolbar_el.model;
-			const type = (model.get('type') == 'chatroom') ? 'groupchat' : 'chat';				
-			const target = model.get('jid');
-			const myself = Strophe.getBareJidFromJid(converseConn.jid);
-											
-			room = _converse.api.settings.get('voicechat').prefix.toLocaleLowerCase() + cyrb53((model.get('type') == 'chatroom') ? target : (myself < target ? myself + target : target + myself));
-			button = toolbar_el.querySelector('.plugin-voicechat');
-
-			console.debug("voicechat is clicked", model, room, button);
-
-			if (button.classList.contains('blink_me')) {
-				stopVoiceChat(model);						
-			} else {
-				startVoiceChat();						
-			}	
-		}			
-	}
-
-	function stopVoiceChat() {			
-		if (localTracks){
-			for (let i = 0; i < localTracks.length; i++) {
-				try {				
-					localTracks[i].dispose();
-				} catch (e) {};
-			}
-			localTracks = null;			
-		}
-		
-		if (jitsiRoom) {
-			jitsiRoom.leave();
-			jitsiRoom = null;
-		}
-		
-		if (connection) {
-			connection.disconnect();
-		}
-		let voiceChatAudio = document.getElementById('voicechat-audio');
-		if (voiceChatAudio) voiceChatAudio.innerHTML = "";	
-		
-		if (button && button.classList.contains('blink_me')) {
-			button.classList.remove('blink_me');
-			button.title = _converse.api.settings.get('voicechat').start;
-			model.sendMessage({body: '/me ' + _converse.api.settings.get('voicechat').stopped});			
-		}
-		
-		if (recognitionActive && recognition)
-		{
-			recognition.stop();
-			recognitionActive = false;
-		}
-		
-		if (harker) {
-			harker.stop();
-		}
-	}
 	
-	function startVoiceChat() {			
-		if (!localTracks) {		
-			JitsiMeetJS.init({disableAudioLevels: true});
-			
-			JitsiMeetJS.createLocalTracks({ devices: [ 'audio'] }).then(onLocalTracks).catch(error => {
-				console.error("startVoiceChat", error);
-			});			
-		} else {
-			goConnect();
-		}	
-	}
-	
-	function goConnect() {
-		const options = _converse.api.settings.get('voicechat');			
-		console.debug("goConnect", room, localTracks, options);		
-		connection = new JitsiMeetJS.JitsiConnection(null, null, options);
-		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,  onConnectionSuccess);
-		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED,       onConnectionFailed);
-		connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, onConnectionDisconnected);
-
-		JitsiMeetJS.mediaDevices.addEventListener(JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED, onDeviceListChanged);
-		connection.connect();			
-	}
-	
-	function getLocalStream() {
-		return document.getElementById('localAudio0')?.srcObject;	
-	}	
-	
-	function onLocalTracks(tracks) {
-		localTracks = tracks;		
-		console.debug("onLocalTracks", room, localTracks);
-		
-		let voiceChatAudio = document.getElementById('voicechat-audio');
-		if (!voiceChatAudio) voiceChatAudio = newElement('div', 'voicechat-audio');
+		const toolbar_el = converse.env.utils.ancestor(ev.target, 'converse-chat-toolbar');
+		const chatview = _converse.chatboxviews.get(toolbar_el.model.get('jid'));		
+		console.debug("performAudio", chatview, toolbar_el.model);
 		
-		for (let i = 0; i < localTracks.length; i++) 
-		{
-			if (localTracks[i].getType() === 'audio') {			
-				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,	audioLevel => console.debug(`Audio Level local: ${audioLevel}`));
-				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_MUTE_CHANGED, () => console.debug('local track muted'));
-				localTracks[i].addEventListener(JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED,() => console.debug('local track stoped'));
-				localTracks[i].addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED, deviceId => {console.debug(`local track audio output device was changed to ${deviceId}`)	});
+		const jid = toolbar_el.model.get("jid");
+		const id = toolbar_el.model.get("box_id");
+		const occupants = chatview.querySelector('.occupants');	
 
-				const localAudio = newElement('span', `local-audio-${i}`, `<audio autoplay='1' muted='true' id='localAudio${i}' />`);
-				voiceChatAudio.append(localAudio);
-				localTracks[i].attach($(`#localAudio${i}`)[0]);				
-			}
-			
-			if (i == 0) {
-
-				if (_converse.api.settings.get('voicechat').transcribe) {
-					setupSpeechRecognition();
-				}					
-
-				harker = hark(localTracks[i].stream, {interval: 100, history: 4 });
+		console.debug("performAudio", jid, id, occupants, chatview);	
 
-				harker.on('speaking', () => {
-					
-					if (_converse.api.settings.get('voicechat').transcribe && model) {					
-						model.setChatState(_converse.COMPOSING);											
-					}
-				});
+		const button = toolbar_el.querySelector('.plugin-voicechat');
+        const chatroom_body = chatview.querySelector('.chatroom-body');
+        let info_area = chatview.querySelector('.occupants-voice-chat');
 
-				harker.on('stopped_speaking', () =>  {
-					
-					if (_converse.api.settings.get('voicechat').transcribe && model) {						
-						model.setChatState(_converse.PAUSED);												
-					}
-				});						
-			}
-		}
+		console.debug("toggleInfoBar", chatview, jid, id);
 		
-		goConnect();		
-	}
-
-	function onRemoteTrack(track) {
-		if (track.isLocal() || track.getType() === 'video') {
-			return;
-		}
-		let voiceChatAudio = document.getElementById('voicechat-audio');		
-		const participant = track.getParticipantId();
-
-		if (!remoteTracks[participant]) {
-			remoteTracks[participant] = [];
-		}
-		const idx = remoteTracks[participant].push(track);
-		console.debug("onRemoteTrack", room, remoteTracks);
-		
-		track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,	audioLevel => console.debug(`Audio Level remote: ${audioLevel}`));
-		track.addEventListener(JitsiMeetJS.events.track.TRACK_MUTE_CHANGED,	() => console.debug('remote track muted'));
-		track.addEventListener(JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED, () => console.debug('remote track stoped'));
-		track.addEventListener(JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,	deviceId =>	console.debug(`remote track audio output device was changed to ${deviceId}`));
-
-		const id = participant + track.getType() + idx;
-		const localAudio = newElement('span', `remote-audio-${participant}-${idx}`, `<audio autoplay='1' id='${participant}audio${idx}' />`);
-		voiceChatAudio.append(localAudio);		
-		track.attach($(`#${id}`)[0]);		
-	}
-	
-	function onDeviceListChanged(devices) {
-		console.info('onDeviceListChanged', devices);
-	}
-	
-	function onConnectionSuccess() {
-		console.debug("onConnectionSuccess", room);		
-		
-		jitsiRoom = connection.initJitsiConference(room, {});	
-		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
-		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_REMOVED, track => {console.debug(`track removed!!!${track}`)	});
-		jitsiRoom.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
-		jitsiRoom.on(JitsiMeetJS.events.conference.USER_JOINED, id => {console.debug('user join'); remoteTracks[id] = []	});
-		jitsiRoom.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
-		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_MUTE_CHANGED, track => {console.debug(`${track.getType()} - ${track.isMuted()}`)	});
-		jitsiRoom.on(JitsiMeetJS.events.conference.DISPLAY_NAME_CHANGED, (userID, displayName) => console.debug(`${userID} - ${displayName}`));
-		jitsiRoom.on(JitsiMeetJS.events.conference.TRACK_AUDIO_LEVEL_CHANGED, (userID, audioLevel) => console.debug(`${userID} - ${audioLevel}`));
-		jitsiRoom.on(JitsiMeetJS.events.conference.PHONE_NUMBER_CHANGED, () => console.debug(`${jitsiRoom.getPhoneNumber()} - ${jitsiRoom.getPhonePin()}`));
-		
-		jitsiRoom.join();
-	}
-
-	function onConnectionFailed() {
-		console.error('onConnectionFailed');
-	}
-
-	function onConnectionDisconnected() {
-		console.debug('onConnectionDisconnected!');
-		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,  onConnectionSuccess);
-		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED,       onConnectionFailed);
-		connection.removeEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, onConnectionDisconnected);
-	}
-
-	function onConferenceJoined() {
-		console.debug('onConferenceJoined!', jitsiRoom, localTracks);
-		
-		for (let i = 0; i < localTracks.length; i++) {
-			jitsiRoom.addTrack(localTracks[i]);
-		}
-		
-		if (button) {
-			button.classList.add('blink_me');	
-			button.title = _converse.api.settings.get('voicechat').stop;
-			model.sendMessage({body: '/me ' + _converse.api.settings.get('voicechat').started});			
-		}					
-	}
-
-	function onUserLeft(id) {
-		console.debug('onUserLeft', id);
-		
-		if (!remoteTracks[id]) {
-			return;
-		}
-		const tracks = remoteTracks[id];
-
-		for (let i = 0; i < tracks.length; i++) {
-			tracks[i].detach($(`#${id}${tracks[i].getType()}`));
-		}
-	}
-
-	function newElement(el, id, html, className) {
-		const ele = document.createElement(el);
-		if (id) ele.id = id;
-		if (html) ele.innerHTML = html;
-		if (className) ele.classList.add(className);
-		document.body.appendChild(ele);
-		return ele;
-	}
-	
-    function setupSpeechRecognition() {
-        console.debug("setupSpeechRecognition");
-
-        recognition = new webkitSpeechRecognition();
-        recognition.lang = _converse.api.settings.get('voicechat').transcribeLanguage;
-        recognition.continuous = true;
-        recognition.interimResults = false;
-
-        recognition.onresult = function(event)
-        {
-            console.debug("Speech recog event", event)
-
-            if (event.results[event.resultIndex].isFinal==true)
-            {
-                const transcript = event.results[event.resultIndex][0].transcript;
-                console.debug("Speech recog transcript", transcript);
-                if (model) model.sendMessage({'body': transcript});		
-				if (model) model.setChatState(_converse.ACTIVE);					
-			}
-        }
-
-        recognition.onspeechend  = function(event)
-        {
-            console.debug("Speech recog onspeechend", event);		
-        }
-
-        recognition.onstart = function(event)
+        if (!info_area)
         {
-            console.debug("Speech to text started", event);
-            recognitionActive = true;			
+            info_area = document.createElement("div");
+            info_area.classList.add('occupants-voice-chat'); // col-xs-12 col-md-4 col-xl-2
+			info_area.style.display = "none";				
+			info_area.style.width = "500px";			
+            info_area.classList.add('col-xs-12');
+            info_area.classList.add('col-md-4');
+            info_area.classList.add('col-xl-2');			
+            chatroom_body.appendChild(info_area);
         }
 
-        recognition.onend = function(event)
-        {
-            console.debug("Speech to text ended", event);
-
-            if (recognitionActive)
-            {
-                console.debug("Speech to text restarted");
-                setTimeout(function() {recognition.start()}, 1000);
-            }
-        }
-
-        recognition.onerror = function(event)
-        {
-            console.debug("Speech to text error", event);
-        }
+		if (info_area.style.display == "none") {
+			chatview.model.save({'hidden_occupants': true});			
+			const style = "width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden;";
+			console.debug("performAudio iframe", voicechatServer, jid);
+			info_area.innerHTML = '<iframe allow="geolocation; microphone; camera" style="' + style + '" src="/community-plugins/packages/voicechat/ohun/index.html?server=' + voicechatServer + '#converse_' + jid.split("@")[0] + '"></iframe>';
+			info_area.style.display = "";	
+			button.classList.add('blink_me');		
 
-        recognition.start();		
+		} else {
+			chatview.model.save({'hidden_occupants': false});
+			info_area.innerHTML = "";			
+			info_area.style.display = "none";
+			button.classList.remove('blink_me');
+		}
+		
+        chatview.scrollDown();
     }	
 	
 }));

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác