From 76367bbd25431c0c1f68de3538837474142a863a Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Apr 2024 23:10:09 +0800 Subject: [PATCH] :sparkles: Voice Chat yo! --- index.html | 1 + src/components/chat/ChatEditor.vue | 18 ++-- .../chat/channels/ChannelAction.vue | 2 +- src/layouts/chat.vue | 43 ++++++++ src/stores/channels.ts | 28 ++++- src/views/chat/page.vue | 101 +++++++++++++++++- 6 files changed, 177 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index 637dca8..635a6f1 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ + Solian diff --git a/src/components/chat/ChatEditor.vue b/src/components/chat/ChatEditor.vue index 11dfa26..41bbf30 100644 --- a/src/components/chat/ChatEditor.vue +++ b/src/components/chat/ChatEditor.vue @@ -1,10 +1,10 @@ diff --git a/src/layouts/chat.vue b/src/layouts/chat.vue index 5fdfef2..5f71741 100644 --- a/src/layouts/chat.vue +++ b/src/layouts/chat.vue @@ -9,6 +9,23 @@
+ + +
@@ -24,6 +41,7 @@ import { onMounted, ref, watch } from "vue" import { useChannels } from "@/stores/channels" import ChannelAction from "@/components/chat/channels/ChannelAction.vue" import { useUI } from "@/stores/ui" +import { getAtk } from "@/stores/userinfo" const { showErrorSnackbar } = useUI() @@ -31,6 +49,7 @@ const route = useRoute() const channels = useChannels() const loading = ref(false) +const calling = ref(false) async function readMetadata() { loading.value = true @@ -43,6 +62,30 @@ async function readMetadata() { loading.value = false } +async function makeCall() { + calling.value = true + const res = await request("messaging", `/api/channels/${route.params.channel}/calls`, { + method: "POST", + headers: { Authorization: `Bearer ${await getAtk()}` } + }) + if (res.status !== 200) { + showErrorSnackbar(await res.text()) + } + calling.value = false +} + +async function endsCall() { + calling.value = true + const res = await request("messaging", `/api/channels/${route.params.channel}/calls/ongoing`, { + method: "DELETE", + headers: { Authorization: `Bearer ${await getAtk()}` } + }) + if (res.status !== 200) { + showErrorSnackbar(await res.text()) + } + calling.value = false +} + watch( () => route.params.channel, (val) => { diff --git a/src/stores/channels.ts b/src/stores/channels.ts index cb90fe2..7f626f2 100644 --- a/src/stores/channels.ts +++ b/src/stores/channels.ts @@ -35,13 +35,16 @@ export const useChannels = defineStore("channels", () => { const available = ref([]) const current = ref(null) + const messages = ref([]) + const call = ref(null) const route = useRoute() watch( () => route.params.channel, (val) => { if (!val) { + call.value = null messages.value = [] current.value = null } @@ -64,6 +67,22 @@ export const useChannels = defineStore("channels", () => { const ui = useUI() + async function exchangeCallToken() { + if (!(await checkLoggedIn())) return + if (!current.value) return + + const res = await request("messaging", `/api/channels/${current.value.alias}/calls/ongoing/token`, { + method: "POST", + headers: { Authorization: `Bearer ${await getAtk()}` } + }) + if (res.status !== 200) { + ui.showErrorSnackbar(`unable to exchange call token: ${await res.text()}`) + return null + } else { + return await res.json() + } + } + async function connect() { if (!(await checkLoggedIn())) return @@ -112,6 +131,13 @@ export const useChannels = defineStore("channels", () => { return x.id !== payload.id }) break + + case "calls.new": + call.value = payload + break + case "calls.end": + call.value = null + break } } }) @@ -121,5 +147,5 @@ export const useChannels = defineStore("channels", () => { socket.close() } - return { done, show, related, available, current, messages, list, connect, disconnect } + return { done, show, related, available, current, messages, call, list, exchangeCallToken, connect, disconnect } }) \ No newline at end of file diff --git a/src/views/chat/page.vue b/src/views/chat/page.vue index f015c80..ed43eaf 100644 --- a/src/views/chat/page.vue +++ b/src/views/chat/page.vue @@ -7,11 +7,29 @@ - + + + + + + + + + @@ -39,6 +57,20 @@ const loading = ref(false) const pagination = reactive({ page: 1, pageSize: 10, total: 0 }) +async function readCall() { + loading.value = true + const res = await request( + "messaging", + `/api/channels/${route.params.channel}/calls/ongoing` + ) + if (res.status !== 200 && res.status !== 404) { + showErrorSnackbar(await res.text()) + } else { + channels.call = await res.json() + } + loading.value = false +} + async function readHistory() { loading.value = true const res = await request( @@ -86,9 +118,11 @@ watch( () => channels.current, (val) => { if (val) { + unmountJitsi() pagination.page = 1 pagination.total = 0 readHistory() + readCall() } }, { immediate: true } @@ -97,4 +131,61 @@ watch( function scrollTop() { window.scroll({ top: 0 }) } - \ No newline at end of file + +watch( + () => channels.call, + (val) => { + if (!val) { + unmountJitsi() + } + } +) + +let mounted = false +let jitsiInstance: any + +async function mountJitsi() { + if (mounted) return false + if (!channels.call) return + const tk = await channels.exchangeCallToken() + console.log(tk) + if (!tk) return + const domain = tk.endpoint.replace("http://", "").replace("https://", "") + const options = { + roomName: channels.call.external_id, + parentNode: document.querySelector("#call"), + jwt: tk.token + } + // This class imported by the script tag in index.html + // @ts-ignore + jitsiInstance = new JitsiMeetExternalAPI(domain, options) + mounted = true +} + +function unmountJitsi() { + mounted = false + if (jitsiInstance) { + jitsiInstance.dispose() + jitsiInstance = null + } +} + +onUnmounted(() => unmountJitsi()) + + + + + \ No newline at end of file