-
+
-
+
@@ -136,15 +141,30 @@ const joinedMembers = computed(() => {
const joinedCount = computed(() => joinedMembers.value.length)
-/** 加入按钮禁用:自己已经在该房间内(含本端正在 INVITING / RUNNING) */
-const joinDisabled = computed(() => {
+/** 本端是否正在该房间通话(处于 INVITING / RUNNING) */
+const isInThisCall = computed(
+ () => rtcStore.isActive && rtcStore.call?.room === activeCall.value?.room
+)
+
+/**
+ * 服务端是否记录我已加入;刷新后 LiveKit 连接已断但 webhook 还没把 status 标为 LEFT 时仍为 true;
+ * 用于把按钮文案切到「重新加入」,但不 disable 按钮
+ */
+const serverSaysJoined = computed(() => {
const myId = getCurrentUserId()
- if (rtcStore.isActive && rtcStore.call?.room === activeCall.value?.room) {
- return true
- }
return activeCall.value?.joinedUserIds?.includes(myId) ?? false
})
+/** 加入按钮禁用:仅在本端实际持有 LiveKit 连接时禁用 */
+const joinDisabled = computed(() => isInThisCall.value)
+
+/** 加入按钮文案;本端连着 → 已在通话中;服务端还残留我但本端断了 → 重新加入;其它 → 加入 */
+const joinLabel = computed(() => {
+ if (isInThisCall.value) return '已在通话中'
+ if (serverSaysJoined.value) return '重新加入'
+ return '加入'
+})
+
/** 主动加入:调 invite 命中已有 call 拿 token;rtcStore 按 status 自动进 RUNNING */
async function handleJoin() {
const call = activeCall.value
diff --git a/src/views/im/home/composables/useLiveKitRoom.ts b/src/views/im/home/composables/useLiveKitRoom.ts
index 2b0fd5cd3..a9aa24987 100644
--- a/src/views/im/home/composables/useLiveKitRoom.ts
+++ b/src/views/im/home/composables/useLiveKitRoom.ts
@@ -30,6 +30,8 @@ export function useLiveKitRoom() {
const micEnabled = ref(true)
/** 摄像头开关 */
const cameraEnabled = ref(false)
+ /** 扬声器开关;浏览器无系统级 API,通过 audio 元素 muted 属性实现远端音频静音 */
+ const speakerEnabled = ref(true)
/** 屏幕共享开关 */
const screenShareEnabled = ref(false)
/** 当前是否处于「重连中」;瞬断时 UI 显示提示而不强制结束通话 */
@@ -99,7 +101,7 @@ export function useLiveKitRoom() {
.on(RoomEvent.ConnectionQualityChanged, (quality) => {
connectionQuality.value = quality
})
- // 瞬断 → 显示「重连中」;不关通话窗,由 ICE restart 机制恢复
+ // 瞬断 → 显示「重连中」;不关通话窗,由 SDK 内部重连机制恢复
.on(RoomEvent.Reconnecting, () => {
reconnecting.value = true
})
@@ -113,7 +115,7 @@ export function useLiveKitRoom() {
disconnectedHandlers.forEach((cb) => cb())
})
- // 预热 getUserMedia 与 WebSocket 握手并行,省 100~300ms 串行延迟;
+ // 预热 getUserMedia 与 WebSocket 握手并行,省 100~300ms 串行延迟;
// 拿到的 stream 仅用于触发权限弹窗 + 设备就绪,握手完成后由 LiveKit 内部重新请求设备发布轨
const warmup = prewarmMedia(opts)
// 建立 WebSocket 信令 + WebRTC 媒体通道;完成后 localParticipant 可用,已在房参与者会通过 ParticipantConnected 事件批量推送
@@ -176,6 +178,11 @@ export function useLiveKitRoom() {
cameraEnabled.value = enabled
}
+ /** 切扬声器;仅切响应式状态,实际静音由模板上 audio 元素 :muted 绑定生效 */
+ function setSpeakerEnabled(enabled: boolean) {
+ speakerEnabled.value = enabled
+ }
+
/**
* 切屏幕共享;
*
@@ -264,6 +271,7 @@ export function useLiveKitRoom() {
reconnecting.value = false
micEnabled.value = true
cameraEnabled.value = false
+ speakerEnabled.value = true
screenShareEnabled.value = false
}
@@ -275,12 +283,14 @@ export function useLiveKitRoom() {
connectionQuality,
micEnabled,
cameraEnabled,
+ speakerEnabled,
screenShareEnabled,
reconnecting,
connect,
disconnect,
setMicEnabled,
setCameraEnabled,
+ setSpeakerEnabled,
setScreenShareEnabled,
pickStream,
onDisconnected,