Factor video code and stores out of voice.ts
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
import {Room as LiveKitRoom} from "livekit-client"
|
||||||
|
import {derived, writable} from "svelte/store"
|
||||||
|
import {type Room} from "@app/core/state"
|
||||||
|
|
||||||
|
export type VoiceSession = {
|
||||||
|
url: string
|
||||||
|
h: string
|
||||||
|
room: LiveKitRoom
|
||||||
|
muted: boolean
|
||||||
|
cameraOn: boolean
|
||||||
|
screenShareOn: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Pubkey = string
|
||||||
|
|
||||||
|
export type VoiceParticipant = {pubkey?: Pubkey; identity: string}
|
||||||
|
|
||||||
|
export enum VoiceState {
|
||||||
|
Joining = "joining",
|
||||||
|
Connected = "connected",
|
||||||
|
Disconnected = "disconnected",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const currentVoiceSession = writable<VoiceSession | undefined>(undefined)
|
||||||
|
|
||||||
|
export const voiceState = writable<VoiceState>(VoiceState.Disconnected)
|
||||||
|
|
||||||
|
export const currentVoiceRoom = writable<Room | undefined>(undefined)
|
||||||
|
|
||||||
|
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
||||||
|
|
||||||
|
export const pubkeyFromLiveKitIdentity = (identity: string): string | undefined =>
|
||||||
|
/^[a-f0-9]{64}$/.test(identity.slice(0, 64)) ? identity.slice(0, 64) : undefined
|
||||||
|
|
||||||
|
export const participantFromLiveKitIdentity = (identity: string): VoiceParticipant => {
|
||||||
|
const pk = pubkeyFromLiveKitIdentity(identity)
|
||||||
|
return pk ? {pubkey: pk, identity} : {identity}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const participantKey = (p: VoiceParticipant) => p.pubkey ?? p.identity
|
||||||
|
|
||||||
|
export const speakingParticipants = writable<VoiceParticipant[]>([])
|
||||||
|
|
||||||
|
export const isParticipantSpeaking = derived(
|
||||||
|
speakingParticipants,
|
||||||
|
$participants => (p: VoiceParticipant) =>
|
||||||
|
$participants.some(sp => participantKey(sp) === participantKey(p)),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** True when the local user is in LiveKit’s active-speakers list (currently talking). */
|
||||||
|
export const isLocalSpeaking = derived(
|
||||||
|
[currentVoiceSession, speakingParticipants],
|
||||||
|
([$session, $speaking]) => {
|
||||||
|
if (!$session?.room) return false
|
||||||
|
const local = participantFromLiveKitIdentity($session.room.localParticipant.identity)
|
||||||
|
return $speaking.some(sp => participantKey(sp) === participantKey(local))
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import {Track} from "livekit-client"
|
||||||
|
import {MediaQuery} from "svelte/reactivity"
|
||||||
|
import {derived, get, writable} from "svelte/store"
|
||||||
|
import {currentVoiceSession, VoiceState, type VoiceSession, voiceState} from "@app/call/stores"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
|
export enum VideoCallLayout {
|
||||||
|
Chat = "chat",
|
||||||
|
Video = "video",
|
||||||
|
Split = "split",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isDesktopLayout = new MediaQuery("min-width: 768px", false)
|
||||||
|
|
||||||
|
export enum ViewportSize {
|
||||||
|
Desktop = "desktop",
|
||||||
|
Mobile = "mobile",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const videoCallViewportSync = {
|
||||||
|
previousLayout: undefined as ViewportSize | undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const videoCallLayout = writable<VideoCallLayout>(VideoCallLayout.Split)
|
||||||
|
|
||||||
|
export const resetVideoCallLayout = () => {
|
||||||
|
videoCallViewportSync.previousLayout = undefined
|
||||||
|
videoCallLayout.set(VideoCallLayout.Chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const videoPrimaryTileKey = writable<string | undefined>(undefined)
|
||||||
|
|
||||||
|
export const toggleVideoPrimaryTile = (key: string) => {
|
||||||
|
videoPrimaryTileKey.update(k => (k === key ? undefined : key))
|
||||||
|
}
|
||||||
|
|
||||||
|
const VISUAL_SOURCES = [Track.Source.Camera, Track.Source.ScreenShare] as const
|
||||||
|
|
||||||
|
const countLiveVisualFeeds = (session: VoiceSession): number => {
|
||||||
|
const room = session.room
|
||||||
|
let n = 0
|
||||||
|
const lp = room.localParticipant
|
||||||
|
if (session.cameraOn) {
|
||||||
|
const pub = lp.getTrackPublication(Track.Source.Camera)
|
||||||
|
if (pub?.track) n += 1
|
||||||
|
}
|
||||||
|
if (session.screenShareOn) {
|
||||||
|
const pub = lp.getTrackPublication(Track.Source.ScreenShare)
|
||||||
|
if (pub?.track) n += 1
|
||||||
|
}
|
||||||
|
for (const rp of room.remoteParticipants.values()) {
|
||||||
|
for (const source of VISUAL_SOURCES) {
|
||||||
|
const pub = rp.getTrackPublication(source)
|
||||||
|
if (pub?.isSubscribed && pub.track) n += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
export const triggerVideoFeedCount = () => {
|
||||||
|
currentVoiceSession.update(s => (s ? {...s} : s))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const videoTileCount = derived([currentVoiceSession, voiceState], ([$session, $state]) => {
|
||||||
|
if ($state !== VoiceState.Connected || !$session) return 0
|
||||||
|
return countLiveVisualFeeds($session)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const toggleCamera = async () => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
const cameraOn = !session.cameraOn
|
||||||
|
if (!cameraOn) {
|
||||||
|
session.room.localParticipant.setCameraEnabled(false)
|
||||||
|
currentVoiceSession.set({...session, cameraOn})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await session.room.localParticipant.setCameraEnabled(true)
|
||||||
|
currentVoiceSession.set({...session, cameraOn})
|
||||||
|
} catch (e) {
|
||||||
|
pushToast({theme: "error", message: "Could not access camera"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleScreenShare = async () => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
const screenShareOn = !session.screenShareOn
|
||||||
|
if (!screenShareOn) {
|
||||||
|
session.room.localParticipant.setScreenShareEnabled(false)
|
||||||
|
currentVoiceSession.set({...session, screenShareOn})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await session.room.localParticipant.setScreenShareEnabled(true)
|
||||||
|
currentVoiceSession.set({...session, screenShareOn})
|
||||||
|
} catch (e) {
|
||||||
|
pushToast({theme: "error", message: "Could not start screen sharing"})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,27 @@ import {
|
|||||||
supportsAudioOutputSelection,
|
supportsAudioOutputSelection,
|
||||||
type AudioCaptureOptions,
|
type AudioCaptureOptions,
|
||||||
} from "livekit-client"
|
} from "livekit-client"
|
||||||
import {derived, get, writable} from "svelte/store"
|
import {derived, get} from "svelte/store"
|
||||||
import {map, removeUndefined, uniqBy} from "@welshman/lib"
|
import {map, removeUndefined, uniqBy} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
|
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
|
||||||
import {signer} from "@welshman/app"
|
import {signer} from "@welshman/app"
|
||||||
import {getLivekitEndpoint} from "$lib/livekit"
|
import {getLivekitEndpoint} from "$lib/livekit"
|
||||||
import {AbortError, whenAborted, whenTimeout} from "$lib/util"
|
import {AbortError, whenAborted, whenTimeout} from "$lib/util"
|
||||||
import {deriveLatestEventForUrl, deriveRoom, makeRoomId, type Room} from "@app/core/state"
|
import {
|
||||||
|
currentVoiceRoom,
|
||||||
|
currentVoiceSession,
|
||||||
|
participantFromLiveKitIdentity,
|
||||||
|
participantKey,
|
||||||
|
participantPubkeyMap,
|
||||||
|
pubkeyFromLiveKitIdentity,
|
||||||
|
speakingParticipants,
|
||||||
|
VoiceState,
|
||||||
|
type VoiceParticipant,
|
||||||
|
voiceState,
|
||||||
|
} from "@app/call/stores"
|
||||||
|
import {resetVideoCallLayout, triggerVideoFeedCount, videoPrimaryTileKey} from "@app/call/video"
|
||||||
|
import {deriveLatestEventForUrl, deriveRoom, makeRoomId} from "@app/core/state"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
export const LIVEKIT_PARTICIPANTS = 39004
|
export const LIVEKIT_PARTICIPANTS = 39004
|
||||||
@@ -28,27 +41,6 @@ export {checkRelayHasLivekit} from "$lib/livekit"
|
|||||||
|
|
||||||
export {supportsAudioOutputSelection}
|
export {supportsAudioOutputSelection}
|
||||||
|
|
||||||
export type VoiceSession = {
|
|
||||||
url: string
|
|
||||||
h: string
|
|
||||||
room: LiveKitRoom
|
|
||||||
muted: boolean
|
|
||||||
cameraOn: boolean
|
|
||||||
screenShareOn: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Pubkey = string
|
|
||||||
|
|
||||||
export type VoiceParticipant = {pubkey?: Pubkey; identity: string}
|
|
||||||
|
|
||||||
export enum VoiceState {
|
|
||||||
Joining = "joining",
|
|
||||||
Connected = "connected",
|
|
||||||
Disconnected = "disconnected",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentVoiceSession = writable<VoiceSession | undefined>(undefined)
|
|
||||||
|
|
||||||
const LIVEKIT_DEFAULT_DEVICE_ID = "default"
|
const LIVEKIT_DEFAULT_DEVICE_ID = "default"
|
||||||
|
|
||||||
export enum DeviceKind {
|
export enum DeviceKind {
|
||||||
@@ -83,36 +75,6 @@ export const switchVoiceActiveDevice = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const voiceState = writable<VoiceState>(VoiceState.Disconnected)
|
|
||||||
|
|
||||||
export const currentVoiceRoom = writable<Room | undefined>(undefined)
|
|
||||||
|
|
||||||
/** Chat-only, full-width video, or split (desktop). On narrow viewports, `split` shows as chat until resize remaps it. */
|
|
||||||
export enum VideoCallLayout {
|
|
||||||
Chat = "chat",
|
|
||||||
Video = "video",
|
|
||||||
Split = "split",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const videoCallLayout = writable<VideoCallLayout>(VideoCallLayout.Split)
|
|
||||||
|
|
||||||
const resetVideoCallLayout = () => {
|
|
||||||
videoCallLayout.set(VideoCallLayout.Chat)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
|
||||||
|
|
||||||
/** Spotlight tile id — must match VideoCallContent `tileKey` (identity + source, not trackSid). */
|
|
||||||
export const videoPrimaryTileKey = writable<string | undefined>(undefined)
|
|
||||||
|
|
||||||
export const toggleVideoPrimaryTile = (key: string) => {
|
|
||||||
videoPrimaryTileKey.update(k => (k === key ? undefined : key))
|
|
||||||
}
|
|
||||||
|
|
||||||
const triggerVideoTileCount = () => {
|
|
||||||
currentVoiceSession.update(s => (s ? {...s} : s))
|
|
||||||
}
|
|
||||||
|
|
||||||
const addParticipant = (identity: string) => {
|
const addParticipant = (identity: string) => {
|
||||||
participantPubkeyMap.update(m => {
|
participantPubkeyMap.update(m => {
|
||||||
const next = new Map(m)
|
const next = new Map(m)
|
||||||
@@ -129,34 +91,6 @@ const deleteParticipant = (identity: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pubkeyFromLiveKitIdentity = (identity: string): string | undefined =>
|
|
||||||
/^[a-f0-9]{64}$/.test(identity.slice(0, 64)) ? identity.slice(0, 64) : undefined
|
|
||||||
|
|
||||||
export const participantFromLiveKitIdentity = (identity: string): VoiceParticipant => {
|
|
||||||
const pk = pubkeyFromLiveKitIdentity(identity)
|
|
||||||
return pk ? {pubkey: pk, identity} : {identity}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const participantKey = (p: VoiceParticipant) => p.pubkey ?? p.identity
|
|
||||||
|
|
||||||
export const speakingParticipants = writable<VoiceParticipant[]>([])
|
|
||||||
|
|
||||||
export const isParticipantSpeaking = derived(
|
|
||||||
speakingParticipants,
|
|
||||||
$participants => (p: VoiceParticipant) =>
|
|
||||||
$participants.some(sp => participantKey(sp) === participantKey(p)),
|
|
||||||
)
|
|
||||||
|
|
||||||
/** True when the local user is in LiveKit’s active-speakers list (currently talking). */
|
|
||||||
export const isLocalSpeaking = derived(
|
|
||||||
[currentVoiceSession, speakingParticipants],
|
|
||||||
([$session, $speaking]) => {
|
|
||||||
if (!$session?.room) return false
|
|
||||||
const local = participantFromLiveKitIdentity($session.room.localParticipant.identity)
|
|
||||||
return $speaking.some(sp => participantKey(sp) === participantKey(local))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const fetchLivekitToken = async (
|
const fetchLivekitToken = async (
|
||||||
url: string,
|
url: string,
|
||||||
groupId: string,
|
groupId: string,
|
||||||
@@ -260,14 +194,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) {
|
||||||
triggerVideoTileCount()
|
triggerVideoFeedCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
triggerVideoTileCount()
|
triggerVideoFeedCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,69 +371,3 @@ export const toggleMute = async () => {
|
|||||||
pushToast({theme: "error", message: "Could not access microphone"})
|
pushToast({theme: "error", message: "Could not access microphone"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const VISUAL_SOURCES = [Track.Source.Camera, Track.Source.ScreenShare] as const
|
|
||||||
|
|
||||||
const countLiveVisualFeeds = (session: VoiceSession): number => {
|
|
||||||
const room = session.room
|
|
||||||
let n = 0
|
|
||||||
const lp = room.localParticipant
|
|
||||||
if (session.cameraOn) {
|
|
||||||
const pub = lp.getTrackPublication(Track.Source.Camera)
|
|
||||||
if (pub?.track) n += 1
|
|
||||||
}
|
|
||||||
if (session.screenShareOn) {
|
|
||||||
const pub = lp.getTrackPublication(Track.Source.ScreenShare)
|
|
||||||
if (pub?.track) n += 1
|
|
||||||
}
|
|
||||||
for (const rp of room.remoteParticipants.values()) {
|
|
||||||
for (const source of VISUAL_SOURCES) {
|
|
||||||
const pub = rp.getTrackPublication(source)
|
|
||||||
if (pub?.isSubscribed && pub.track) n += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
export const videoTileCount = derived([currentVoiceSession, voiceState], ([$session, $state]) => {
|
|
||||||
if ($state !== VoiceState.Connected || !$session) return 0
|
|
||||||
return countLiveVisualFeeds($session)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const toggleCamera = async () => {
|
|
||||||
const session = get(currentVoiceSession)
|
|
||||||
if (!session) return
|
|
||||||
|
|
||||||
const cameraOn = !session.cameraOn
|
|
||||||
if (!cameraOn) {
|
|
||||||
session.room.localParticipant.setCameraEnabled(false)
|
|
||||||
currentVoiceSession.set({...session, cameraOn})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await session.room.localParticipant.setCameraEnabled(true)
|
|
||||||
currentVoiceSession.set({...session, cameraOn})
|
|
||||||
} catch (e) {
|
|
||||||
pushToast({theme: "error", message: "Could not access camera"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toggleScreenShare = async () => {
|
|
||||||
const session = get(currentVoiceSession)
|
|
||||||
if (!session) return
|
|
||||||
|
|
||||||
const screenShareOn = !session.screenShareOn
|
|
||||||
if (!screenShareOn) {
|
|
||||||
session.room.localParticipant.setScreenShareEnabled(false)
|
|
||||||
currentVoiceSession.set({...session, screenShareOn})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await session.room.localParticipant.setScreenShareEnabled(true)
|
|
||||||
currentVoiceSession.set({...session, screenShareOn})
|
|
||||||
} catch (e) {
|
|
||||||
pushToast({theme: "error", message: "Could not start screen sharing"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,14 +8,17 @@
|
|||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import VideoCallVideo from "@app/components/VideoCallVideo.svelte"
|
import VideoCallVideo from "@app/components/VideoCallVideo.svelte"
|
||||||
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
||||||
|
import {get} from "svelte/store"
|
||||||
import {
|
import {
|
||||||
currentVoiceSession,
|
|
||||||
currentVoiceRoom,
|
|
||||||
VideoCallLayout,
|
VideoCallLayout,
|
||||||
videoPrimaryTileKey,
|
isDesktopLayout,
|
||||||
toggleVideoPrimaryTile,
|
toggleVideoPrimaryTile,
|
||||||
pubkeyFromLiveKitIdentity,
|
videoCallLayout,
|
||||||
} from "@app/voice"
|
videoCallViewportSync,
|
||||||
|
ViewportSize,
|
||||||
|
videoPrimaryTileKey,
|
||||||
|
} from "@app/call/video"
|
||||||
|
import {currentVoiceSession, currentVoiceRoom, pubkeyFromLiveKitIdentity} from "@app/call/stores"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layout: VideoCallLayout
|
layout: VideoCallLayout
|
||||||
@@ -37,6 +40,23 @@
|
|||||||
|
|
||||||
const {layout, mobile = false, url, h, class: className = ""}: Props = $props()
|
const {layout, mobile = false, url, h, class: className = ""}: Props = $props()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const currentLayout = isDesktopLayout.current ? ViewportSize.Desktop : ViewportSize.Mobile
|
||||||
|
const {previousLayout} = videoCallViewportSync
|
||||||
|
if (previousLayout === undefined) {
|
||||||
|
videoCallViewportSync.previousLayout = currentLayout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (previousLayout === currentLayout) return
|
||||||
|
const p = get(videoCallLayout)
|
||||||
|
if (previousLayout === ViewportSize.Desktop && currentLayout === ViewportSize.Mobile) {
|
||||||
|
if (p === VideoCallLayout.Split) videoCallLayout.set(VideoCallLayout.Video)
|
||||||
|
} else if (previousLayout === ViewportSize.Mobile && currentLayout === ViewportSize.Desktop) {
|
||||||
|
if (p === VideoCallLayout.Chat) videoCallLayout.set(VideoCallLayout.Split)
|
||||||
|
}
|
||||||
|
videoCallViewportSync.previousLayout = currentLayout
|
||||||
|
})
|
||||||
|
|
||||||
const isViewingCurrentCallRoom = $derived(
|
const isViewingCurrentCallRoom = $derived(
|
||||||
$currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
|
$currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<script module lang="ts">
|
|
||||||
import {MediaQuery} from "svelte/reactivity"
|
|
||||||
|
|
||||||
export const isDesktopLayout = new MediaQuery("min-width: 768px", false)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {get} from "svelte/store"
|
|
||||||
import {VideoCallLayout, videoCallLayout} from "@app/voice"
|
|
||||||
|
|
||||||
let prevLayout = $state<boolean | undefined>(undefined)
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const currentLayout = isDesktopLayout.current
|
|
||||||
if (prevLayout === undefined) {
|
|
||||||
prevLayout = currentLayout
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (prevLayout === currentLayout) return
|
|
||||||
const p = get(videoCallLayout)
|
|
||||||
if (prevLayout && !currentLayout) {
|
|
||||||
if (p === VideoCallLayout.Split) videoCallLayout.set(VideoCallLayout.Video)
|
|
||||||
} else if (!prevLayout && currentLayout) {
|
|
||||||
if (p === VideoCallLayout.Chat) videoCallLayout.set(VideoCallLayout.Split)
|
|
||||||
}
|
|
||||||
prevLayout = currentLayout
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -7,13 +7,8 @@
|
|||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||||
import {
|
import {currentVoiceSession, type VoiceSession} from "@app/call/stores"
|
||||||
currentVoiceSession,
|
import {DeviceKind, supportsAudioOutputSelection, switchVoiceActiveDevice} from "@app/call/voice"
|
||||||
DeviceKind,
|
|
||||||
supportsAudioOutputSelection,
|
|
||||||
switchVoiceActiveDevice,
|
|
||||||
type VoiceSession,
|
|
||||||
} from "@app/voice"
|
|
||||||
import {popModal} from "@app/util/modal"
|
import {popModal} from "@app/util/modal"
|
||||||
|
|
||||||
const selectValueForActiveDevice = (session: VoiceSession, kind: DeviceKind): string => {
|
const selectValueForActiveDevice = (session: VoiceSession, kind: DeviceKind): string => {
|
||||||
|
|||||||
@@ -12,14 +12,13 @@
|
|||||||
import {makeRoomId} from "@app/core/state"
|
import {makeRoomId} from "@app/core/state"
|
||||||
import {
|
import {
|
||||||
VoiceState,
|
VoiceState,
|
||||||
deriveVoiceParticipants,
|
|
||||||
cancelJoinVoiceRoom,
|
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
voiceState,
|
|
||||||
isParticipantSpeaking,
|
isParticipantSpeaking,
|
||||||
participantKey,
|
participantKey,
|
||||||
|
voiceState,
|
||||||
type VoiceParticipant,
|
type VoiceParticipant,
|
||||||
} from "@app/voice"
|
} from "@app/call/stores"
|
||||||
|
import {cancelJoinVoiceRoom, deriveVoiceParticipants} from "@app/call/voice"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||||
import {AbortError, TimeoutError} from "$lib/util"
|
import {AbortError, TimeoutError} from "$lib/util"
|
||||||
import {displayRoom} from "@app/core/state"
|
import {displayRoom} from "@app/core/state"
|
||||||
import {joinVoiceRoom} from "@app/voice"
|
import {joinVoiceRoom} from "@app/call/voice"
|
||||||
import {popModal} from "@app/util/modal"
|
import {popModal} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import VoiceCallAudioSettingsDialog from "@app/components/VoiceCallAudioSettingsDialog.svelte"
|
import VoiceCallAudioSettingsDialog from "@app/components/VoiceCallAudioSettingsDialog.svelte"
|
||||||
import VoiceRoomJoinDialog from "@app/components/VoiceRoomJoinDialog.svelte"
|
import VoiceRoomJoinDialog from "@app/components/VoiceRoomJoinDialog.svelte"
|
||||||
import {isDesktopLayout} from "@app/components/VideoCallLayoutViewportSync.svelte"
|
|
||||||
import {
|
import {
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
deriveRoom,
|
deriveRoom,
|
||||||
@@ -32,19 +31,20 @@
|
|||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
import {makeRoomPath} from "@app/util/routes"
|
import {makeRoomPath} from "@app/util/routes"
|
||||||
import {
|
import {
|
||||||
VoiceState,
|
|
||||||
VideoCallLayout,
|
VideoCallLayout,
|
||||||
|
isDesktopLayout,
|
||||||
|
toggleCamera,
|
||||||
|
toggleScreenShare,
|
||||||
|
videoCallLayout,
|
||||||
|
} from "@app/call/video"
|
||||||
|
import {
|
||||||
|
VoiceState,
|
||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
voiceState,
|
voiceState,
|
||||||
videoCallLayout,
|
|
||||||
isLocalSpeaking,
|
isLocalSpeaking,
|
||||||
leaveVoiceRoom,
|
} from "@app/call/stores"
|
||||||
toggleMute,
|
import {cancelJoinVoiceRoom, leaveVoiceRoom, toggleMute} from "@app/call/voice"
|
||||||
toggleCamera,
|
|
||||||
toggleScreenShare,
|
|
||||||
cancelJoinVoiceRoom,
|
|
||||||
} from "@app/voice"
|
|
||||||
|
|
||||||
const {relay, h} = $derived($page.params)
|
const {relay, h} = $derived($page.params)
|
||||||
const url = $derived(relay ? decodeRelay(relay) : undefined)
|
const url = $derived(relay ? decodeRelay(relay) : undefined)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
loadFeedsForPubkey,
|
loadFeedsForPubkey,
|
||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import {hasBlossomSupport} from "@app/core/commands"
|
import {hasBlossomSupport} from "@app/core/commands"
|
||||||
import {LIVEKIT_PARTICIPANTS} from "@app/voice"
|
import {LIVEKIT_PARTICIPANTS} from "@app/call/voice"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
import {syncKeyboard} from "@app/util/keyboard"
|
import {syncKeyboard} from "@app/util/keyboard"
|
||||||
import {getPageTitle} from "@app/util/title"
|
import {getPageTitle} from "@app/util/title"
|
||||||
import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte"
|
import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte"
|
||||||
import VideoCallLayoutViewportSync from "@app/components/VideoCallLayoutViewportSync.svelte"
|
|
||||||
|
|
||||||
const {children} = $props()
|
const {children} = $props()
|
||||||
|
|
||||||
@@ -230,6 +229,5 @@
|
|||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<div class="tippy-target"></div>
|
<div class="tippy-target"></div>
|
||||||
<NewNotificationSound />
|
<NewNotificationSound />
|
||||||
<VideoCallLayoutViewportSync />
|
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -45,14 +45,8 @@
|
|||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
||||||
import VideoCallContent from "@app/components/VideoCallContent.svelte"
|
import VideoCallContent from "@app/components/VideoCallContent.svelte"
|
||||||
import {
|
import {VoiceState, currentVoiceRoom, voiceState} from "@app/call/stores"
|
||||||
VoiceState,
|
import {VideoCallLayout, videoCallLayout, videoTileCount} from "@app/call/video"
|
||||||
VideoCallLayout,
|
|
||||||
currentVoiceRoom,
|
|
||||||
videoTileCount,
|
|
||||||
videoCallLayout,
|
|
||||||
voiceState,
|
|
||||||
} from "@app/voice"
|
|
||||||
import {makeFeed} from "@app/core/requests"
|
import {makeFeed} from "@app/core/requests"
|
||||||
import {popKey} from "@lib/implicit"
|
import {popKey} from "@lib/implicit"
|
||||||
import {checked} from "@app/util/notifications"
|
import {checked} from "@app/util/notifications"
|
||||||
|
|||||||
Reference in New Issue
Block a user