Files
ysj7705andCursor 47a7a717ec
CI / Type-check & Build (push) Successful in 10m31s
CI / Build & Push image (push) Successful in 13m5s
raise screen share quality and stop the grid from overflowing
Screen share was capped by WebRTC's default ~2.5 Mbps and by window
capture resolution, and fixed aspect-video cards forced the page to
scroll. Adds minimal [sejong-rtc] logs to diagnose ICE failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 16:39:04 +09:00

104 lines
3.3 KiB
Vue

<script setup lang="ts">
import type { ActiveStream } from '~/types/room'
const props = defineProps<{
streams: ActiveStream[]
remoteStreams: Record<string, MediaStream>
localPreview?: { streamId: string; stream: MediaStream; title: string; startedAt: number } | null
layoutMode: 'grid' | 'focus'
focusStreamId: string | null
volumeMap: Record<string, number>
mutedMap: Record<string, boolean>
viewerNamesByStream: Record<string, string[]>
}>()
const emit = defineEmits<{
'update:volume': [streamId: string, value: number]
'update:muted': [streamId: string, value: boolean]
stop: [streamId: string]
focus: [streamId: string]
}>()
const cards = computed(() => {
const list: Array<{
streamId: string
title: string
mediaStream: MediaStream | null
startedAt: number
isLocal?: boolean
}> = []
if (props.localPreview) {
list.push({
streamId: props.localPreview.streamId,
title: props.localPreview.title,
mediaStream: props.localPreview.stream,
startedAt: props.localPreview.startedAt,
isLocal: true,
})
}
for (const s of props.streams) {
if (props.localPreview?.streamId === s.streamId) continue
list.push({
streamId: s.streamId,
title: s.broadcasterNickname,
mediaStream: props.remoteStreams[s.streamId] || null,
startedAt: s.startedAt,
})
}
if (props.layoutMode === 'focus' && props.focusStreamId) {
return [...list].sort((a, b) => {
if (a.streamId === props.focusStreamId) return -1
if (b.streamId === props.focusStreamId) return 1
return 0
})
}
return list
})
// Cells stretch to the available height; the video letterboxes itself with object-contain,
// so the aspect ratio is kept without forcing the page to scroll.
const base = 'grid h-full min-h-0 gap-3 auto-rows-[minmax(220px,1fr)]'
const gridClass = computed(() => {
if (props.layoutMode === 'focus') {
return `${base} grid-cols-1 lg:grid-cols-4 lg:grid-rows-3`
}
const n = cards.value.length
if (n <= 1) return `${base} grid-cols-1`
if (n === 2) return `${base} grid-cols-1 md:grid-cols-2`
if (n <= 4) return `${base} grid-cols-1 md:grid-cols-2`
return `${base} grid-cols-1 md:grid-cols-2 xl:grid-cols-3`
})
</script>
<template>
<div :class="gridClass">
<RoomVideoPlayerCard
v-for="(card, index) in cards"
:key="card.streamId"
:class="layoutMode === 'focus' && index === 0 ? 'lg:col-span-3 lg:row-span-3' : ''"
:stream-id="card.streamId"
:title="card.isLocal ? `${card.title} (미리보기)` : card.title"
:media-stream="card.mediaStream"
:started-at="card.startedAt"
:viewer-names="viewerNamesByStream[card.streamId] || []"
:volume="volumeMap[card.streamId] ?? 1"
:muted="mutedMap[card.streamId] ?? false"
:focused="layoutMode === 'focus' ? index === 0 : focusStreamId === card.streamId"
@update:volume="emit('update:volume', card.streamId, $event)"
@update:muted="emit('update:muted', card.streamId, $event)"
@stop="emit('stop', card.streamId)"
@focus="emit('focus', card.streamId)"
/>
<div
v-if="!cards.length"
class="flex items-center justify-center rounded-lg border border-dashed border-[var(--border)] text-sm text-[var(--text-muted)]"
>
시청할 화면을 왼쪽에서 선택하세요.
</div>
</div>
</template>