add video to livekit calls
This commit is contained in:
+88
-4
@@ -22,6 +22,7 @@ export type VoiceSession = {
|
||||
h: string
|
||||
room: Room
|
||||
muted: boolean
|
||||
cameraOn: boolean
|
||||
}
|
||||
|
||||
export type Pubkey = string
|
||||
@@ -38,6 +39,11 @@ export const currentVoiceRoom = writable<{url: string; h: string} | undefined>(u
|
||||
|
||||
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)
|
||||
|
||||
const bumpVideoCallLayoutRevision = () => videoCallLayoutRevision.update(n => n + 1)
|
||||
|
||||
const addParticipant = (identity: string) => {
|
||||
participantPubkeyMap.update(m => {
|
||||
const next = new Map(m)
|
||||
@@ -133,6 +139,7 @@ export const deriveVoiceParticipants = (url: string, h: string) =>
|
||||
)
|
||||
|
||||
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
||||
videoCallLayoutRevision.set(0)
|
||||
speakingParticipants.set([])
|
||||
participantPubkeyMap.set(new Map())
|
||||
currentVoiceSession.set(undefined)
|
||||
@@ -152,11 +159,16 @@ const onTrackSubscribed = (track: Track) => {
|
||||
element.style.display = "none"
|
||||
document.body.appendChild(element)
|
||||
element.play().catch(() => {})
|
||||
} else if (track.kind === Track.Kind.Video) {
|
||||
bumpVideoCallLayoutRevision()
|
||||
}
|
||||
}
|
||||
|
||||
const onTrackUnsubscribed = (track: Track) => {
|
||||
track.detach().forEach(el => el.remove())
|
||||
if (track.kind === Track.Kind.Video) {
|
||||
bumpVideoCallLayoutRevision()
|
||||
}
|
||||
}
|
||||
|
||||
const onActiveSpeakersChanged = (participants: {identity: string}[]) => {
|
||||
@@ -238,7 +250,7 @@ export const joinVoiceRoom = async (url: string, h: string): Promise<void> => {
|
||||
pushToast({theme: "error", message: "Could not access microphone"})
|
||||
}
|
||||
|
||||
currentVoiceSession.set({url, h, room, muted})
|
||||
currentVoiceSession.set({url, h, room, muted, cameraOn: false})
|
||||
voiceState.set("connected")
|
||||
playJoinSound()
|
||||
} catch (e) {
|
||||
@@ -257,11 +269,20 @@ export const leaveVoiceRoom = async () => {
|
||||
const audio = new Audio("/leave-voice-room.mp3")
|
||||
audio.play().catch(() => {})
|
||||
|
||||
if (session.cameraOn) {
|
||||
try {
|
||||
await session.room.localParticipant.setCameraEnabled(false)
|
||||
} catch {
|
||||
/* pass */
|
||||
}
|
||||
}
|
||||
|
||||
voiceState.set("disconnected")
|
||||
videoCallLayoutRevision.set(0)
|
||||
currentVoiceSession.set(undefined)
|
||||
session.room.disconnect()
|
||||
speakingParticipants.set([])
|
||||
participantPubkeyMap.set(new Map())
|
||||
voiceState.set("disconnected")
|
||||
session.room.disconnect()
|
||||
currentVoiceSession.set(undefined)
|
||||
}
|
||||
|
||||
export const rejoinVoiceRoom = () => {
|
||||
@@ -288,3 +309,66 @@ export const toggleMute = async () => {
|
||||
pushToast({theme: "error", message: "Could not access microphone"})
|
||||
}
|
||||
}
|
||||
|
||||
const roomHasSubscribedRemoteCamera = (room: Room): boolean => {
|
||||
for (const p of room.remoteParticipants.values()) {
|
||||
const pub = p.getTrackPublication(Track.Source.Camera)
|
||||
if (pub?.isSubscribed && pub.track) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** True when the connected session has local camera on or any subscribed remote camera track. */
|
||||
export const videoCallContentActive = derived(
|
||||
[currentVoiceSession, voiceState, videoCallLayoutRevision],
|
||||
([$session, $state, _rev]) => {
|
||||
if ($state !== "connected" || !$session) return false
|
||||
if ($session.cameraOn) return true
|
||||
return roomHasSubscribedRemoteCamera($session.room)
|
||||
},
|
||||
)
|
||||
|
||||
/** Live camera tracks (local + remote) for layout automation. */
|
||||
const countLiveCameraFeeds = (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
|
||||
}
|
||||
for (const rp of room.remoteParticipants.values()) {
|
||||
const pub = rp.getTrackPublication(Track.Source.Camera)
|
||||
if (pub?.isSubscribed && pub.track) n += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
export const videoTileCount = derived(
|
||||
[currentVoiceSession, voiceState, videoCallLayoutRevision],
|
||||
([$session, $state, _rev]) => {
|
||||
if ($state !== "connected" || !$session) return 0
|
||||
return countLiveCameraFeeds($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})
|
||||
bumpVideoCallLayoutRevision()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await session.room.localParticipant.setCameraEnabled(true)
|
||||
currentVoiceSession.set({...session, cameraOn})
|
||||
bumpVideoCallLayoutRevision()
|
||||
} catch (e) {
|
||||
pushToast({theme: "error", message: "Could not access camera"})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user