This commit is contained in:
1
client/app/components.d.ts
vendored
1
client/app/components.d.ts
vendored
@@ -22,6 +22,7 @@ declare module 'vue' {
|
|||||||
PrimeSelect: typeof import('primevue/select')['default']
|
PrimeSelect: typeof import('primevue/select')['default']
|
||||||
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
|
PrimeTag: typeof import('primevue/tag')['default']
|
||||||
PrimeToast: typeof import('primevue/toast')['default']
|
PrimeToast: typeof import('primevue/toast')['default']
|
||||||
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const inputMuted = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const hasBadges = computed(() => {
|
const hasBadges = computed(() => {
|
||||||
return shareConsumers.value.length > 0
|
return streaming.value
|
||||||
|| premuted.value
|
|| premuted.value
|
||||||
|| inputMuted.value
|
|| inputMuted.value
|
||||||
|| props.client.outputMuted
|
|| props.client.outputMuted
|
||||||
|
|||||||
@@ -1,19 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cursor-pointer hover:outline outline-primary rounded overflow-hidden flex items-center justify-center">
|
<div
|
||||||
<video :srcObject="mediaStream" muted autoplay />
|
class="relative rounded overflow-hidden flex items-center justify-center"
|
||||||
|
:class="{
|
||||||
|
'group cursor-pointer hover:outline outline-primary': !isMe,
|
||||||
|
}"
|
||||||
|
@click="watch"
|
||||||
|
>
|
||||||
|
<video :srcObject="stream" muted autoplay />
|
||||||
|
|
||||||
|
<PrimeTag
|
||||||
|
severity="secondary"
|
||||||
|
class="absolute bottom-2 text-sm opacity-70"
|
||||||
|
:class="{
|
||||||
|
'group-hover:opacity-100': !isMe,
|
||||||
|
}"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
{{ isMe ? 'You' : client.username }}
|
||||||
|
</PrimeTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Consumer } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
consumer: Consumer
|
client: ChadClient
|
||||||
|
stream: MediaStream
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const mediaStream = computed(() => {
|
const { me } = useClients()
|
||||||
return new MediaStream([props.consumer.raw.track])
|
const fullscreenVideo = useFullscreenVideo()
|
||||||
|
|
||||||
|
const isMe = computed(() => {
|
||||||
|
return props.client.socketId === me.value?.socketId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function watch() {
|
||||||
|
if (isMe.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
fullscreenVideo.show(props.stream)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ export function useClient(socketId: MaybeRef<ChadClient['socketId']>) {
|
|||||||
return Object.values(mediasoup.consumers.value).filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return Object.values(mediasoup.consumers.value).filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
const producers = computed(() => {
|
|
||||||
return mediasoup.producers.value.values().filter(producer => producer.appData.socketId === client.value.socketId).toArray()
|
|
||||||
})
|
|
||||||
|
|
||||||
const audioConsumers = computed(() => {
|
const audioConsumers = computed(() => {
|
||||||
return mediasoup.audioConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return mediasoup.audioConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
@@ -30,6 +26,10 @@ export function useClient(socketId: MaybeRef<ChadClient['socketId']>) {
|
|||||||
return mediasoup.shareConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
return mediasoup.shareConsumers.value.filter(consumer => consumer.appData.socketId === client.value.socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const producers = computed(() => {
|
||||||
|
return Object.values(mediasoup.producers.value).filter(producer => producer.appData.socketId === client.value.socketId)
|
||||||
|
})
|
||||||
|
|
||||||
const streaming = computed(() => {
|
const streaming = computed(() => {
|
||||||
return videoConsumers.value.length > 0 || shareConsumers.value.length > 0
|
return videoConsumers.value.length > 0 || shareConsumers.value.length > 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -384,6 +384,9 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: {
|
video: {
|
||||||
deviceId: { exact: preferences.videoDeviceId.value },
|
deviceId: { exact: preferences.videoDeviceId.value },
|
||||||
|
width: { ideal: 1920 },
|
||||||
|
height: { ideal: 1080 },
|
||||||
|
frameRate: { ideal: 60 },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -395,9 +398,9 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
await createProducer({
|
await createProducer({
|
||||||
track,
|
track,
|
||||||
streamId: 'mic-video',
|
streamId: 'mic-video',
|
||||||
codec: device.value.rtpCapabilities.codecs?.find(
|
// codec: device.value.rtpCapabilities.codecs?.find(
|
||||||
c => c.mimeType.toLowerCase() === 'video/AV1',
|
// c => c.mimeType.toLowerCase() === 'video/AV1',
|
||||||
),
|
// ),
|
||||||
// codecOptions: {
|
// codecOptions: {
|
||||||
// videoGoogleStartBitrate: 1000,
|
// videoGoogleStartBitrate: 1000,
|
||||||
// },
|
// },
|
||||||
|
|||||||
@@ -1,38 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-[1fr_1fr] gap-2">
|
<div class="grid grid-cols-[1fr_1fr] gap-2">
|
||||||
<template v-for="(group, id) in groupedConsumers" :key="id">
|
|
||||||
<GalleryCard
|
<GalleryCard
|
||||||
v-for="consumer in group"
|
v-for="item in gallery"
|
||||||
:key="consumer.id"
|
:key="item.client.socketId"
|
||||||
:consumer="consumer"
|
:client="item.client"
|
||||||
@click="watch(consumer)"
|
:stream="item.stream"
|
||||||
|
@click="watch(item.stream)"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Consumer } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
|
|
||||||
|
interface GalleryItem {
|
||||||
|
client: ChadClient
|
||||||
|
stream: MediaStream
|
||||||
|
}
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Gallery',
|
name: 'Gallery',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { videoConsumers, shareConsumers } = useMediasoup()
|
const { videoProducer, shareProducer } = useMediasoup()
|
||||||
const fullscreenVideo = useFullscreenVideo()
|
const { clients, me } = useClients()
|
||||||
|
|
||||||
const groupedConsumers = computed<Partial<Record<string, Consumer[]>>>(() => {
|
const gallery = computed(() => {
|
||||||
if (fullscreenVideo.visible.value)
|
return clients.value.reduce<GalleryItem[]>(
|
||||||
return {}
|
(acc, client) => {
|
||||||
|
const { streaming, videoConsumers, shareConsumers } = useClient(client.socketId)
|
||||||
|
|
||||||
const consumers = [...videoConsumers.value, ...shareConsumers.value]
|
if (!streaming.value)
|
||||||
|
return acc
|
||||||
|
|
||||||
return Object.groupBy(consumers, (consumer) => {
|
for (const consumer of [...videoConsumers.value, ...shareConsumers.value]) {
|
||||||
return consumer.appData.socketId!
|
acc.push({
|
||||||
|
client,
|
||||||
|
stream: new MediaStream([consumer.raw.track]),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[videoProducer.value, shareProducer.value].reduce<GalleryItem[]>((acc, producer) => {
|
||||||
|
if (!me.value || !producer || !producer.raw.track)
|
||||||
|
return acc
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
client: me.value,
|
||||||
|
stream: new MediaStream([producer.raw.track]),
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, []),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function watch(consumer: Consumer) {
|
watch(gallery, (gallery) => {
|
||||||
fullscreenVideo.show(new MediaStream([consumer.raw.track]))
|
if (gallery.length > 0)
|
||||||
}
|
return
|
||||||
|
|
||||||
|
navigateTo({ name: 'Index' })
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "chad",
|
"productName": "chad",
|
||||||
"version": "0.2.23",
|
"version": "0.2.24",
|
||||||
"identifier": "xyz.koptilnya.chad",
|
"identifier": "xyz.koptilnya.chad",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../.output/public",
|
"frontendDist": "../.output/public",
|
||||||
|
|||||||
Reference in New Issue
Block a user