fix: video blink when toggling mic mute in calls (#277)
This commit is contained in:
@@ -6,11 +6,13 @@ export type VoiceSession = {
|
|||||||
url: string
|
url: string
|
||||||
h: string
|
h: string
|
||||||
room: LiveKitRoom
|
room: LiveKitRoom
|
||||||
muted: boolean
|
|
||||||
cameraOn: boolean
|
cameraOn: boolean
|
||||||
screenShareOn: boolean
|
screenShareOn: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Mic mute state is separate so toggling it does not re-render video tiles. */
|
||||||
|
export const voiceMicMuted = writable(true)
|
||||||
|
|
||||||
export type Pubkey = string
|
export type Pubkey = string
|
||||||
|
|
||||||
export type VoiceParticipant = {pubkey?: Pubkey; identity: string}
|
export type VoiceParticipant = {pubkey?: Pubkey; identity: string}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
type AudioCaptureOptions,
|
type AudioCaptureOptions,
|
||||||
} from "livekit-client"
|
} from "livekit-client"
|
||||||
import {derived, get} from "svelte/store"
|
import {derived, get} from "svelte/store"
|
||||||
import {map, removeUndefined, uniqBy} from "@welshman/lib"
|
import {map, not, 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"
|
||||||
@@ -22,6 +22,7 @@ import {AbortError, whenAborted, whenTimeout} from "$lib/util"
|
|||||||
import {
|
import {
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
|
voiceMicMuted,
|
||||||
participantFromLiveKitIdentity,
|
participantFromLiveKitIdentity,
|
||||||
participantKey,
|
participantKey,
|
||||||
participantPubkeyMap,
|
participantPubkeyMap,
|
||||||
@@ -173,6 +174,7 @@ const setUpMicrophone = async (
|
|||||||
|
|
||||||
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
const onRoomDisconnected = (reason?: DisconnectReason) => {
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
|
voiceMicMuted.set(true)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVideoCallLayout()
|
resetVideoCallLayout()
|
||||||
if (reason !== undefined && reason !== DisconnectReason.CLIENT_INITIATED) {
|
if (reason !== undefined && reason !== DisconnectReason.CLIENT_INITIATED) {
|
||||||
@@ -295,11 +297,11 @@ export const joinVoiceRoom = async (
|
|||||||
|
|
||||||
const muted = await setUpMicrophone(startMuted, preferredMicId, liveKitRoom.localParticipant)
|
const muted = await setUpMicrophone(startMuted, preferredMicId, liveKitRoom.localParticipant)
|
||||||
|
|
||||||
|
voiceMicMuted.set(muted)
|
||||||
currentVoiceSession.set({
|
currentVoiceSession.set({
|
||||||
url,
|
url,
|
||||||
h,
|
h,
|
||||||
room: liveKitRoom,
|
room: liveKitRoom,
|
||||||
muted,
|
|
||||||
cameraOn: false,
|
cameraOn: false,
|
||||||
screenShareOn: false,
|
screenShareOn: false,
|
||||||
})
|
})
|
||||||
@@ -339,6 +341,7 @@ export const leaveVoiceRoom = async () => {
|
|||||||
|
|
||||||
voiceState.set(VoiceState.Disconnected)
|
voiceState.set(VoiceState.Disconnected)
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
|
voiceMicMuted.set(true)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVideoCallLayout()
|
resetVideoCallLayout()
|
||||||
session.room.disconnect()
|
session.room.disconnect()
|
||||||
@@ -356,18 +359,17 @@ export const toggleMute = async () => {
|
|||||||
const session = get(currentVoiceSession)
|
const session = get(currentVoiceSession)
|
||||||
if (!session) return
|
if (!session) return
|
||||||
|
|
||||||
const muted = !session.muted
|
voiceMicMuted.update(not)
|
||||||
if (muted) {
|
if (get(voiceMicMuted)) {
|
||||||
// Disable and re-enable microphone to trigger permission prompt
|
// Disable and re-enable microphone to trigger permission prompt
|
||||||
session.room.localParticipant.setMicrophoneEnabled(false)
|
session.room.localParticipant.setMicrophoneEnabled(false)
|
||||||
currentVoiceSession.set({...session, muted})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await session.room.localParticipant.setMicrophoneEnabled(true)
|
await session.room.localParticipant.setMicrophoneEnabled(true)
|
||||||
currentVoiceSession.set({...session, muted})
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
voiceMicMuted.set(true)
|
||||||
pushToast({theme: "error", message: "Could not access microphone"})
|
pushToast({theme: "error", message: "Could not access microphone"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,8 +216,8 @@
|
|||||||
data-tip={pinned ? "Exit spotlight" : "Spotlight"}
|
data-tip={pinned ? "Exit spotlight" : "Spotlight"}
|
||||||
aria-pressed={pinned}
|
aria-pressed={pinned}
|
||||||
class={cx(
|
class={cx(
|
||||||
"absolute right-1 top-1 z-20 btn btn-xs btn-square btn-ghost",
|
"absolute right-1 top-1 z-20 btn btn-xs btn-square",
|
||||||
pinned ? "btn-active bg-primary/25 text-primary" : "bg-base-100/70",
|
pinned ? "btn-primary" : "btn-ghost bg-base-100",
|
||||||
)}
|
)}
|
||||||
onclick={spotlightHandlerFor(tileKey(tile))}>
|
onclick={spotlightHandlerFor(tileKey(tile))}>
|
||||||
<Icon icon={Pin} size={3} />
|
<Icon icon={Pin} size={3} />
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
VoiceState,
|
VoiceState,
|
||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
|
voiceMicMuted,
|
||||||
voiceState,
|
voiceState,
|
||||||
isLocalSpeaking,
|
isLocalSpeaking,
|
||||||
} from "@app/call/stores"
|
} from "@app/call/stores"
|
||||||
@@ -183,18 +184,17 @@
|
|||||||
</Button>
|
</Button>
|
||||||
{:else if $voiceState === VoiceState.Connected && $currentVoiceSession}
|
{:else if $voiceState === VoiceState.Connected && $currentVoiceSession}
|
||||||
<Button
|
<Button
|
||||||
data-tip={$currentVoiceSession.muted ? "Unmute" : "Mute"}
|
data-tip={$voiceMicMuted ? "Unmute" : "Mute"}
|
||||||
class={cx(
|
class={cx(
|
||||||
mediaToggleClass,
|
mediaToggleClass,
|
||||||
"overflow-visible",
|
"overflow-visible",
|
||||||
!$currentVoiceSession.muted && $isLocalSpeaking && "text-primary",
|
!$voiceMicMuted && $isLocalSpeaking && "text-primary",
|
||||||
$currentVoiceSession.muted &&
|
$voiceMicMuted && "text-error ring-1 ring-error/50 ring-offset-0 ring-offset-base-100",
|
||||||
"text-error ring-1 ring-error/50 ring-offset-0 ring-offset-base-100",
|
|
||||||
)}
|
)}
|
||||||
onclick={toggleMute}>
|
onclick={toggleMute}>
|
||||||
<span class="relative inline-flex items-center justify-center overflow-visible">
|
<span class="relative inline-flex items-center justify-center overflow-visible">
|
||||||
<Icon icon={Microphone} size={4} />
|
<Icon icon={Microphone} size={4} />
|
||||||
{#if $currentVoiceSession.muted}
|
{#if $voiceMicMuted}
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute inset-0 flex items-center justify-center overflow-visible"
|
class="pointer-events-none absolute inset-0 flex items-center justify-center overflow-visible"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
|
|||||||
Reference in New Issue
Block a user