143 lines
3.8 KiB
Vue
143 lines
3.8 KiB
Vue
<template>
|
|
<div
|
|
class="overflow-hidden rounded-xl transition-[background-color]"
|
|
:class="{
|
|
'hover:bg-surface-800 cursor-pointer': !isMe,
|
|
'bg-surface-800': expanded,
|
|
}"
|
|
>
|
|
<div class="p-3" @click="toggleExpand">
|
|
<div class="flex items-center gap-3">
|
|
<PrimeAvatar
|
|
size="small"
|
|
class="shrink-0"
|
|
:class="{
|
|
'outline-1 outline-primary outline-offset-2': speaking,
|
|
}"
|
|
>
|
|
<template #icon>
|
|
<User :size="20" />
|
|
</template>
|
|
</PrimeAvatar>
|
|
|
|
<p class="flex-1 text-sm leading-5 font-medium text-color truncate w-0">
|
|
{{ client.displayName || client.username }}
|
|
</p>
|
|
|
|
<Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />
|
|
</div>
|
|
|
|
<div v-if="hasBadges" class="flex justify-end align-center gap-1 mt-2">
|
|
<PrimeBadge v-if="streaming" v-tooltip.top="'Watch'" severity="success" value="Streaming" size="small" @click.stop="watchStream" />
|
|
|
|
<PrimeBadge v-if="premuted" severity="danger" value="Muted" size="small" />
|
|
<PrimeBadge v-else-if="client.outputMuted" severity="info" value="No sound" size="small" />
|
|
<PrimeBadge v-else-if="inputMuted" severity="info" value="Muted" size="small" />
|
|
|
|
<PrimeBadge v-if="isMe" severity="secondary" value="You" size="small" class="shrink-0" />
|
|
</div>
|
|
</div>
|
|
|
|
<CollapseTransition v-if="!isMe">
|
|
<div v-if="expanded">
|
|
<div class="px-3 pb-3">
|
|
<div class="flex justify-between text-sm mb-3">
|
|
<span>Volume</span>
|
|
<span>{{ volume }}</span>
|
|
</div>
|
|
<PrimeSlider v-model="volume" :min="0" :max="1000" :step="volume < 200 ? 5 : 25" />
|
|
|
|
<div class="mt-3 flex gap-1 justify-end">
|
|
<PrimeButton size="small" variant="text" @click="premuted = !premuted">
|
|
{{ premuted ? 'Unmute' : 'Mute' }}
|
|
</PrimeButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CollapseTransition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ChadClient } from '#shared/types'
|
|
import { ChevronDown, ChevronUp, User } from 'lucide-vue-next'
|
|
import CollapseTransition from '~/components/CollapseTransition.vue'
|
|
|
|
const props = defineProps<{
|
|
client: ChadClient
|
|
}>()
|
|
|
|
const { outputMuted } = useApp()
|
|
const { consumers: allConsumers, micProducer } = useMediasoup()
|
|
const { me } = useClients()
|
|
const { show } = useFullscreenVideo()
|
|
|
|
const expanded = ref(false)
|
|
|
|
const {
|
|
volume,
|
|
premuted,
|
|
speaking,
|
|
audioConsumers,
|
|
videoConsumers,
|
|
shareConsumers,
|
|
streaming,
|
|
} = useClient(toRef(() => props.client.socketId))
|
|
|
|
const isMe = computed(() => {
|
|
return me.value && props.client.userId === me.value.userId
|
|
})
|
|
|
|
const audioConsumer = computed(() => {
|
|
return audioConsumers.value[0]
|
|
})
|
|
|
|
const audioTrack = computed(() => {
|
|
return audioConsumer.value?.raw.track
|
|
})
|
|
|
|
const audioConsumerPaused = computed(() => {
|
|
if (Object.keys(allConsumers.value).length === 0)
|
|
return false
|
|
|
|
return audioConsumer.value?.paused ?? false
|
|
})
|
|
|
|
const inputMuted = computed(() => {
|
|
if (isMe.value)
|
|
return micProducer.value?.paused ?? false
|
|
|
|
return premuted.value || audioConsumerPaused.value
|
|
})
|
|
|
|
const hasBadges = computed(() => {
|
|
return shareConsumers.value.length > 0
|
|
|| premuted.value
|
|
|| inputMuted.value
|
|
|| props.client.outputMuted
|
|
|| isMe.value
|
|
})
|
|
|
|
const { setGain } = useAudioContext(audioTrack)
|
|
|
|
watchEffect(() => {
|
|
setGain((outputMuted.value || premuted.value) ? 0 : (volume.value * 0.01))
|
|
})
|
|
|
|
function toggleExpand() {
|
|
if (isMe.value)
|
|
return
|
|
|
|
expanded.value = !expanded.value
|
|
}
|
|
|
|
function watchStream() {
|
|
if (!streaming.value)
|
|
return
|
|
|
|
const consumer = [...videoConsumers.value, ...shareConsumers.value][0]!
|
|
|
|
show(new MediaStream([consumer.raw.track]))
|
|
}
|
|
</script>
|