Clean up VideoCallContent
This commit is contained in:
@@ -12,7 +12,6 @@
|
|||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
VideoCallLayout,
|
VideoCallLayout,
|
||||||
videoCallLayoutRevision,
|
|
||||||
videoPrimaryTileKey,
|
videoPrimaryTileKey,
|
||||||
toggleVideoPrimaryTile,
|
toggleVideoPrimaryTile,
|
||||||
pubkeyFromLiveKitIdentity,
|
pubkeyFromLiveKitIdentity,
|
||||||
@@ -42,29 +41,27 @@
|
|||||||
$currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
|
$currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
|
||||||
)
|
)
|
||||||
|
|
||||||
const showPanel = $derived(
|
const showVideoContent = $derived(
|
||||||
isViewingCurrentCallRoom &&
|
isViewingCurrentCallRoom &&
|
||||||
(mobile
|
(mobile
|
||||||
? layout === VideoCallLayout.Video
|
? layout === VideoCallLayout.Video
|
||||||
: layout === VideoCallLayout.Split || layout === VideoCallLayout.Video),
|
: layout === VideoCallLayout.Split || layout === VideoCallLayout.Video),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tiles = $derived.by(() => {
|
const videoTiles = $derived.by(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- re-run when remote video subscribes
|
|
||||||
$videoCallLayoutRevision
|
|
||||||
const session = $currentVoiceSession
|
const session = $currentVoiceSession
|
||||||
if (!session || $currentVoiceRoom?.url !== url || $currentVoiceRoom?.h !== h) {
|
if (!session || $currentVoiceRoom?.url !== url || $currentVoiceRoom?.h !== h) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = session.room
|
const room = session.room
|
||||||
const out: VideoTile[] = []
|
const videoTiles: VideoTile[] = []
|
||||||
const lp = room.localParticipant
|
const user = room.localParticipant
|
||||||
|
|
||||||
if (session.cameraOn) {
|
if (session.cameraOn) {
|
||||||
const localPub = lp.getTrackPublication(Track.Source.Camera)
|
const localPub = user.getTrackPublication(Track.Source.Camera)
|
||||||
out.push({
|
videoTiles.push({
|
||||||
identity: lp.identity,
|
identity: user.identity,
|
||||||
isLocal: true,
|
isLocal: true,
|
||||||
trackSid: localPub?.trackSid ?? "local-camera",
|
trackSid: localPub?.trackSid ?? "local-camera",
|
||||||
attachable: localPub?.track,
|
attachable: localPub?.track,
|
||||||
@@ -73,9 +70,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (session.screenShareOn) {
|
if (session.screenShareOn) {
|
||||||
const localPub = lp.getTrackPublication(Track.Source.ScreenShare)
|
const localPub = user.getTrackPublication(Track.Source.ScreenShare)
|
||||||
out.push({
|
videoTiles.push({
|
||||||
identity: lp.identity,
|
identity: user.identity,
|
||||||
isLocal: true,
|
isLocal: true,
|
||||||
trackSid: localPub?.trackSid ?? "local-screen",
|
trackSid: localPub?.trackSid ?? "local-screen",
|
||||||
attachable: localPub?.track,
|
attachable: localPub?.track,
|
||||||
@@ -86,7 +83,7 @@
|
|||||||
for (const rp of room.remoteParticipants.values()) {
|
for (const rp of room.remoteParticipants.values()) {
|
||||||
const camPub = rp.getTrackPublication(Track.Source.Camera)
|
const camPub = rp.getTrackPublication(Track.Source.Camera)
|
||||||
if (camPub?.isSubscribed && camPub.track) {
|
if (camPub?.isSubscribed && camPub.track) {
|
||||||
out.push({
|
videoTiles.push({
|
||||||
identity: rp.identity,
|
identity: rp.identity,
|
||||||
isLocal: false,
|
isLocal: false,
|
||||||
trackSid: camPub.trackSid,
|
trackSid: camPub.trackSid,
|
||||||
@@ -96,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
const screenPub = rp.getTrackPublication(Track.Source.ScreenShare)
|
const screenPub = rp.getTrackPublication(Track.Source.ScreenShare)
|
||||||
if (screenPub?.isSubscribed && screenPub.track) {
|
if (screenPub?.isSubscribed && screenPub.track) {
|
||||||
out.push({
|
videoTiles.push({
|
||||||
identity: rp.identity,
|
identity: rp.identity,
|
||||||
isLocal: false,
|
isLocal: false,
|
||||||
trackSid: screenPub.trackSid,
|
trackSid: screenPub.trackSid,
|
||||||
@@ -106,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return videoTiles
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Identity + source only — LiveKit can change trackSid after publish, which broke spotlight + stale-key effect. */
|
/** Identity + source only — LiveKit can change trackSid after publish, which broke spotlight + stale-key effect. */
|
||||||
@@ -115,29 +112,29 @@
|
|||||||
const primaryTile = $derived.by(() => {
|
const primaryTile = $derived.by(() => {
|
||||||
const k = $videoPrimaryTileKey
|
const k = $videoPrimaryTileKey
|
||||||
if (k === undefined) return undefined
|
if (k === undefined) return undefined
|
||||||
return tiles.find(t => tileKey(t) === k)
|
return videoTiles.find(t => tileKey(t) === k)
|
||||||
})
|
})
|
||||||
|
|
||||||
const secondaryTiles = $derived.by(() => {
|
const secondaryTiles = $derived.by(() => {
|
||||||
const p = primaryTile
|
const p = primaryTile
|
||||||
if (p === undefined) return tiles
|
if (p === undefined) return videoTiles
|
||||||
const pk = tileKey(p)
|
const pk = tileKey(p)
|
||||||
return tiles.filter(t => tileKey(t) !== pk)
|
return videoTiles.filter(t => tileKey(t) !== pk)
|
||||||
})
|
})
|
||||||
|
|
||||||
const useSpotlightLayout = $derived(primaryTile !== undefined)
|
const useSpotlightLayout = $derived(primaryTile !== undefined)
|
||||||
const useMultiGrid = $derived(!useSpotlightLayout && tiles.length > 2)
|
const useMultiGrid = $derived(!useSpotlightLayout && videoTiles.length > 2)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const k = $videoPrimaryTileKey
|
const k = $videoPrimaryTileKey
|
||||||
if (k === undefined) return
|
if (k === undefined) return
|
||||||
if (!tiles.some(t => tileKey(t) === k)) {
|
if (!videoTiles.some(t => tileKey(t) === k)) {
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
for (const t of tiles) {
|
for (const t of videoTiles) {
|
||||||
const pk = pubkeyFromLiveKitIdentity(t.identity)
|
const pk = pubkeyFromLiveKitIdentity(t.identity)
|
||||||
if (pk) loadProfile(pk)
|
if (pk) loadProfile(pk)
|
||||||
}
|
}
|
||||||
@@ -149,7 +146,7 @@
|
|||||||
return source === Track.Source.ScreenShare ? `${name} · screen` : name
|
return source === Track.Source.ScreenShare ? `${name} · screen` : name
|
||||||
}
|
}
|
||||||
|
|
||||||
const showTileGrid = $derived(tiles.length > 0)
|
const showTileGrid = $derived(videoTiles.length > 0)
|
||||||
|
|
||||||
const spotlightHandlerFor = (key: string) => () => {
|
const spotlightHandlerFor = (key: string) => () => {
|
||||||
toggleVideoPrimaryTile(key)
|
toggleVideoPrimaryTile(key)
|
||||||
@@ -190,7 +187,7 @@
|
|||||||
class="pointer-events-none absolute bottom-1 left-1 max-w-[calc(100%-0.5rem)] truncate rounded bg-base-100/80 px-1.5 py-0.5 text-xs">
|
class="pointer-events-none absolute bottom-1 left-1 max-w-[calc(100%-0.5rem)] truncate rounded bg-base-100/80 px-1.5 py-0.5 text-xs">
|
||||||
{labelFor(tile.identity, tile.source)}{tile.isLocal ? " (you)" : ""}
|
{labelFor(tile.identity, tile.source)}{tile.isLocal ? " (you)" : ""}
|
||||||
</span>
|
</span>
|
||||||
{#if tiles.length > 1}
|
{#if videoTiles.length > 1}
|
||||||
{@const pinned = $videoPrimaryTileKey === tileKey(tile)}
|
{@const pinned = $videoPrimaryTileKey === tileKey(tile)}
|
||||||
<Button
|
<Button
|
||||||
data-tip={pinned ? "Exit spotlight" : "Spotlight"}
|
data-tip={pinned ? "Exit spotlight" : "Spotlight"}
|
||||||
@@ -223,13 +220,13 @@
|
|||||||
{:else if useMultiGrid}
|
{:else if useMultiGrid}
|
||||||
<div
|
<div
|
||||||
class="grid min-h-0 flex-1 grid-cols-1 content-start gap-2 overflow-y-auto sm:grid-cols-2">
|
class="grid min-h-0 flex-1 grid-cols-1 content-start gap-2 overflow-y-auto sm:grid-cols-2">
|
||||||
{#each tiles as tile (tileKey(tile))}
|
{#each videoTiles as tile (tileKey(tile))}
|
||||||
{@render videoTile(tile, "default")}
|
{@render videoTile(tile, "default")}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
|
<div class="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
|
||||||
{#each tiles as tile (tileKey(tile))}
|
{#each videoTiles as tile (tileKey(tile))}
|
||||||
{@render videoTile(tile, "default")}
|
{@render videoTile(tile, "default")}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +240,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#if showPanel}
|
{#if showVideoContent}
|
||||||
<div class={panelChrome}>
|
<div class={panelChrome}>
|
||||||
{#if mobile}
|
{#if mobile}
|
||||||
<div class="flex min-h-0 flex-1 flex-col gap-2">
|
<div class="flex min-h-0 flex-1 flex-col gap-2">
|
||||||
|
|||||||
+9
-20
@@ -102,9 +102,6 @@ const resetVideoCallLayout = () => {
|
|||||||
|
|
||||||
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
||||||
|
|
||||||
/** Bumps when remote video is subscribed/unsubscribed so layout/video UI can react. */
|
|
||||||
export const videoCallLayoutRevision = writable(0)
|
|
||||||
|
|
||||||
/** Spotlight tile id — must match VideoCallContent `tileKey` (identity + source, not trackSid). */
|
/** Spotlight tile id — must match VideoCallContent `tileKey` (identity + source, not trackSid). */
|
||||||
export const videoPrimaryTileKey = writable<string | undefined>(undefined)
|
export const videoPrimaryTileKey = writable<string | undefined>(undefined)
|
||||||
|
|
||||||
@@ -112,7 +109,9 @@ export const toggleVideoPrimaryTile = (key: string) => {
|
|||||||
videoPrimaryTileKey.update(k => (k === key ? undefined : key))
|
videoPrimaryTileKey.update(k => (k === key ? undefined : key))
|
||||||
}
|
}
|
||||||
|
|
||||||
const bumpVideoCallLayoutRevision = () => videoCallLayoutRevision.update(n => n + 1)
|
const triggerVideoTileCount = () => {
|
||||||
|
currentVoiceSession.update(s => (s ? {...s} : s))
|
||||||
|
}
|
||||||
|
|
||||||
const addParticipant = (identity: string) => {
|
const addParticipant = (identity: string) => {
|
||||||
participantPubkeyMap.update(m => {
|
participantPubkeyMap.update(m => {
|
||||||
@@ -239,7 +238,6 @@ const setUpMicrophone = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
||||||
videoCallLayoutRevision.set(0)
|
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVideoCallLayout()
|
resetVideoCallLayout()
|
||||||
@@ -262,14 +260,14 @@ const onTrackSubscribed = (track: Track) => {
|
|||||||
document.body.appendChild(element)
|
document.body.appendChild(element)
|
||||||
element.play().catch(() => {})
|
element.play().catch(() => {})
|
||||||
} else if (track.kind === Track.Kind.Video) {
|
} else if (track.kind === Track.Kind.Video) {
|
||||||
bumpVideoCallLayoutRevision()
|
triggerVideoTileCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTrackUnsubscribed = (track: Track) => {
|
const onTrackUnsubscribed = (track: Track) => {
|
||||||
track.detach().forEach(el => el.remove())
|
track.detach().forEach(el => el.remove())
|
||||||
if (track.kind === Track.Kind.Video) {
|
if (track.kind === Track.Kind.Video) {
|
||||||
bumpVideoCallLayoutRevision()
|
triggerVideoTileCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +298,6 @@ const onLocalTrackUnpublished = (
|
|||||||
if (!session || participant.identity !== session.room.localParticipant.identity) return
|
if (!session || participant.identity !== session.room.localParticipant.identity) return
|
||||||
if (!session.screenShareOn) return
|
if (!session.screenShareOn) return
|
||||||
currentVoiceSession.set({...session, screenShareOn: false})
|
currentVoiceSession.set({...session, screenShareOn: false})
|
||||||
bumpVideoCallLayoutRevision()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let joinAbortController: AbortController | undefined
|
let joinAbortController: AbortController | undefined
|
||||||
@@ -407,7 +404,6 @@ export const leaveVoiceRoom = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
voiceState.set(VoiceState.Disconnected)
|
voiceState.set(VoiceState.Disconnected)
|
||||||
videoCallLayoutRevision.set(0)
|
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVideoCallLayout()
|
resetVideoCallLayout()
|
||||||
@@ -465,13 +461,10 @@ const countLiveVisualFeeds = (session: VoiceSession): number => {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
export const videoTileCount = derived(
|
export const videoTileCount = derived([currentVoiceSession, voiceState], ([$session, $state]) => {
|
||||||
[currentVoiceSession, voiceState, videoCallLayoutRevision],
|
if ($state !== VoiceState.Connected || !$session) return 0
|
||||||
([$session, $state, _rev]) => {
|
return countLiveVisualFeeds($session)
|
||||||
if ($state !== VoiceState.Connected || !$session) return 0
|
})
|
||||||
return countLiveVisualFeeds($session)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export const toggleCamera = async () => {
|
export const toggleCamera = async () => {
|
||||||
const session = get(currentVoiceSession)
|
const session = get(currentVoiceSession)
|
||||||
@@ -481,14 +474,12 @@ export const toggleCamera = async () => {
|
|||||||
if (!cameraOn) {
|
if (!cameraOn) {
|
||||||
session.room.localParticipant.setCameraEnabled(false)
|
session.room.localParticipant.setCameraEnabled(false)
|
||||||
currentVoiceSession.set({...session, cameraOn})
|
currentVoiceSession.set({...session, cameraOn})
|
||||||
bumpVideoCallLayoutRevision()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await session.room.localParticipant.setCameraEnabled(true)
|
await session.room.localParticipant.setCameraEnabled(true)
|
||||||
currentVoiceSession.set({...session, cameraOn})
|
currentVoiceSession.set({...session, cameraOn})
|
||||||
bumpVideoCallLayoutRevision()
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
pushToast({theme: "error", message: "Could not access camera"})
|
pushToast({theme: "error", message: "Could not access camera"})
|
||||||
}
|
}
|
||||||
@@ -502,14 +493,12 @@ export const toggleScreenShare = async () => {
|
|||||||
if (!screenShareOn) {
|
if (!screenShareOn) {
|
||||||
session.room.localParticipant.setScreenShareEnabled(false)
|
session.room.localParticipant.setScreenShareEnabled(false)
|
||||||
currentVoiceSession.set({...session, screenShareOn})
|
currentVoiceSession.set({...session, screenShareOn})
|
||||||
bumpVideoCallLayoutRevision()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await session.room.localParticipant.setScreenShareEnabled(true)
|
await session.room.localParticipant.setScreenShareEnabled(true)
|
||||||
currentVoiceSession.set({...session, screenShareOn})
|
currentVoiceSession.set({...session, screenShareOn})
|
||||||
bumpVideoCallLayoutRevision()
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
pushToast({theme: "error", message: "Could not start screen sharing"})
|
pushToast({theme: "error", message: "Could not start screen sharing"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,9 @@
|
|||||||
if (prevVideoTileCount === 0 && n >= 1) {
|
if (prevVideoTileCount === 0 && n >= 1) {
|
||||||
videoCallLayout.set(VideoCallLayout.Video)
|
videoCallLayout.set(VideoCallLayout.Video)
|
||||||
}
|
}
|
||||||
|
if (prevVideoTileCount >= 1 && n === 0 && $videoCallLayout === VideoCallLayout.Split) {
|
||||||
|
videoCallLayout.set(VideoCallLayout.Chat)
|
||||||
|
}
|
||||||
prevVideoTileCount = n
|
prevVideoTileCount = n
|
||||||
})
|
})
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|||||||
Reference in New Issue
Block a user