Group voice and video calls with MatrixRTC and LiveKit
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
Every call in the web client now goes through MatrixRTC with a LiveKit
SFU, the model Element Call and Element X use; the legacy 1:1 m.call.*
calls are gone. In a room of two the other person is rung and can answer
or decline; in a bigger room a banner offers to join the call. Calls show
a grid of participant tiles with mute, camera, screen sharing and leave,
and media is end-to-end encrypted in encrypted rooms.
Server:
- MSC4140 delayed events (send/state with a delay, plus the
delayed_events list/restart/cancel/send API), stored and scheduled, so
a member whose client drops off is taken out of the call.
- POST /user/{userId}/openid/request_token, which lk-jwt-service uses to
hand out LiveKit tokens.
- org.matrix.msc4143.rtc_foci in /.well-known/matrix/client, configured
under client_api.rtc.
- Room presets let members send call membership state.
- Migration 0014 for both SQL dialects.
Deploy: LiveKit and lk-jwt-service in compose (calls.yml) and the helm
chart. Docs: docs/calls.md, configuration and deployment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
325431a5be
commit
76dfc98a86
49 files changed
+3849
-432
No files matched your search
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import type { Track } from 'livekit-client'
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { WIcon } from '@warp/ui'
|
||||
import MxcAvatar from '@/components/MxcAvatar.vue'
|
||||
import type { Tile } from '@/stores/call'
|
||||
|
||||
// One person in a group call: their screen or camera, else their avatar,
|
||||
// with their name, a muted microphone, and a ring while they speak.
|
||||
|
||||
const props = defineProps<{ tile: Tile; compact?: boolean }>()
|
||||
|
||||
const videoEl = ref<HTMLVideoElement>()
|
||||
const audioEl = ref<HTMLAudioElement>()
|
||||
|
||||
const shown = computed(() => props.tile.screen ?? props.tile.camera)
|
||||
// Your own camera is mirrored, the way a mirror (and every call app) shows you.
|
||||
const mirrored = computed(() => props.tile.local && !props.tile.screen)
|
||||
|
||||
function bind(el: HTMLMediaElement | undefined, track: Track | undefined, old: Track | undefined) {
|
||||
if (!el) return
|
||||
if (old && old !== track) old.detach(el)
|
||||
if (track) track.attach(el)
|
||||
}
|
||||
|
||||
watch([videoEl, shown], ([el, track], [, old]) => bind(el, track, old), { flush: 'post', immediate: true })
|
||||
watch([audioEl, () => props.tile.audio], ([el, track], [, old]) => bind(el, track, old), { flush: 'post', immediate: true })
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (videoEl.value) shown.value?.detach(videoEl.value)
|
||||
if (audioEl.value) props.tile.audio?.detach(audioEl.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'relative flex min-h-0 items-center justify-center overflow-hidden rounded-md bg-[#1d2126] transition-shadow',
|
||||
tile.speaking ? 'shadow-[0_0_0_3px_#3fb68b]' : '',
|
||||
]"
|
||||
>
|
||||
<audio v-if="tile.audio" ref="audioEl" autoplay class="hidden" />
|
||||
<video
|
||||
v-show="shown"
|
||||
ref="videoEl"
|
||||
autoplay
|
||||
playsinline
|
||||
muted
|
||||
:class="['h-full w-full', tile.screen ? 'object-contain' : 'object-cover', mirrored && '-scale-x-100']"
|
||||
/>
|
||||
<MxcAvatar v-if="!shown" :name="tile.name" :mxc="tile.avatarMxc" :size="compact ? 48 : 88" />
|
||||
<div class="absolute bottom-2 left-2 flex max-w-[calc(100%-1rem)] items-center gap-1 rounded-sm bg-[#000000a6] px-2 py-0.5">
|
||||
<WIcon v-if="tile.micMuted" name="mic-off" :size="14" label="Muted" class="shrink-0 text-[#ff8a80]" />
|
||||
<WIcon v-if="tile.screen" name="screen-share" :size="14" label="Sharing their screen" class="shrink-0" />
|
||||
<span class="caption truncate text-[#eceeee]">{{ tile.local ? `${tile.name} (you)` : tile.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user