Skip to content

Commit 59753fb

Browse files
authored
feat: push to talk (#28)
* feat: push to talk * refactor: better description * refactor: better description
1 parent f8722d7 commit 59753fb

4 files changed

Lines changed: 74 additions & 3 deletions

File tree

frontend/src/components/settings/AudioSettingsTab.vue

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<template>
2-
<SettingsLayoutBase
3-
:description="'Configure audio processing options for your microphone'"
4-
>
2+
<SettingsLayoutBase :description="'Configure your audio and microphone settings'">
53
<template #title>
64
Audio
75
</template>
@@ -13,6 +11,12 @@
1311
description="Reduce background noise from your microphone"
1412
v-model="noiseCancellationEnabledLocal"
1513
/>
14+
<Switch
15+
class="w-full !px-0"
16+
label="Push to Talk"
17+
description="Hold spacebar to unmute your microphone"
18+
v-model="pushToTalkEnabledLocal"
19+
/>
1620
</div>
1721
</template>
1822
</SettingsLayoutBase>
@@ -23,7 +27,9 @@ import { Switch } from "frappe-ui";
2327
import { type Ref, ref, watch } from "vue";
2428
import {
2529
noiseCancellationEnabled,
30+
pushToTalkEnabled,
2631
setNoiseCancellationEnabled,
32+
setPushToTalkEnabled,
2733
} from "../../data/mediaPreferences";
2834
import SettingsLayoutBase from "./SettingsLayoutBase.vue";
2935
@@ -38,4 +44,14 @@ watch(noiseCancellationEnabledLocal, (newValue) => {
3844
watch(noiseCancellationEnabled, (newValue) => {
3945
noiseCancellationEnabledLocal.value = newValue;
4046
});
47+
48+
const pushToTalkEnabledLocal: Ref<boolean> = ref(pushToTalkEnabled.value);
49+
50+
watch(pushToTalkEnabledLocal, (newValue) => {
51+
setPushToTalkEnabled(newValue);
52+
});
53+
54+
watch(pushToTalkEnabled, (newValue) => {
55+
pushToTalkEnabledLocal.value = newValue;
56+
});
4157
</script>

frontend/src/composables/useMeetingLogic.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
noiseCancellationEnabled,
66
cameraEnabled as prefCameraEnabled,
77
micEnabled as prefMicEnabled,
8+
pushToTalkEnabled,
89
selectedCameraId,
910
selectedMicId,
1011
selectedSpeakerId,
@@ -62,6 +63,7 @@ export function useMeetingLogic(meetingState, meetingId, options = {}) {
6263
const realtimeListenersSetup = ref(false);
6364
const activeSpeakerTimeout = ref(null);
6465
const joiningInProgress = ref(false);
66+
const unmutedByPushToTalk = ref(false);
6567

6668
// Background effects
6769
const { applyBackgroundEffects, stopProcessing, processedStream } =
@@ -2119,6 +2121,25 @@ export function useMeetingLogic(meetingState, meetingId, options = {}) {
21192121
* Handle keyboard shortcuts
21202122
*/
21212123
const handleKeyDown = (event) => {
2124+
const targetTag = event.target?.tagName?.toLowerCase();
2125+
const isInput =
2126+
targetTag === "input" ||
2127+
targetTag === "textarea" ||
2128+
event.target?.isContentEditable;
2129+
2130+
if (
2131+
pushToTalkEnabled.value &&
2132+
event.code === "Space" &&
2133+
!isInput &&
2134+
!event.repeat
2135+
) {
2136+
event.preventDefault();
2137+
if (!meetingState.isMicOn.value) {
2138+
unmutedByPushToTalk.value = true;
2139+
toggleMicrophone();
2140+
}
2141+
}
2142+
21222143
if ((event.metaKey || event.ctrlKey) && event.key === "d") {
21232144
event.preventDefault();
21242145
toggleMicrophone();
@@ -2129,6 +2150,23 @@ export function useMeetingLogic(meetingState, meetingId, options = {}) {
21292150
}
21302151
};
21312152

2153+
const handleKeyUp = (event) => {
2154+
const targetTag = event.target?.tagName?.toLowerCase();
2155+
const isInput =
2156+
targetTag === "input" ||
2157+
targetTag === "textarea" ||
2158+
event.target?.isContentEditable;
2159+
2160+
if (pushToTalkEnabled.value && event.code === "Space" && !isInput) {
2161+
if (unmutedByPushToTalk.value) {
2162+
unmutedByPushToTalk.value = false;
2163+
if (meetingState.isMicOn.value) {
2164+
toggleMicrophone();
2165+
}
2166+
}
2167+
}
2168+
};
2169+
21322170
// ==================== NOTIFICATION CONTEXT WATCHERS ====================
21332171

21342172
// Watch chat state to update notification context
@@ -2286,5 +2324,6 @@ export function useMeetingLogic(meetingState, meetingId, options = {}) {
22862324

22872325
// Methods - Keyboard
22882326
handleKeyDown,
2327+
handleKeyUp,
22892328
};
22902329
}

frontend/src/data/mediaPreferences.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export const noiseCancellationEnabled: Ref<boolean> = ref(
2828
readBool("mediaPref.noiseCancellation", false),
2929
);
3030

31+
export const pushToTalkEnabled: Ref<boolean> = ref(
32+
readBool("mediaPref.pushToTalk", false),
33+
);
34+
3135
export function setNoiseCancellationEnabled(val: boolean): void {
3236
noiseCancellationEnabled.value = !!val;
3337
localStorage.setItem(
@@ -36,6 +40,14 @@ export function setNoiseCancellationEnabled(val: boolean): void {
3640
);
3741
}
3842

43+
export function setPushToTalkEnabled(val: boolean): void {
44+
pushToTalkEnabled.value = !!val;
45+
localStorage.setItem(
46+
"mediaPref.pushToTalk",
47+
pushToTalkEnabled.value ? "1" : "0",
48+
);
49+
}
50+
3951
export function setMicEnabled(val: boolean): void {
4052
micEnabled.value = !!val;
4153
localStorage.setItem("mediaPref.mic", micEnabled.value ? "1" : "0");
@@ -71,4 +83,5 @@ export function loadMediaPreferences(): void {
7183
"mediaPref.noiseCancellation",
7284
false,
7385
);
86+
pushToTalkEnabled.value = readBool("mediaPref.pushToTalk", false);
7487
}

frontend/src/pages/Meeting.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ const {
241241
setupRaiseHandEvents,
242242
toggleRaiseHand,
243243
handleKeyDown,
244+
handleKeyUp,
244245
sfuManager,
245246
applySpeakerDevice,
246247
processedStream,
@@ -657,6 +658,7 @@ const handleDeviceChanged = async (event) => {
657658
// Lifecycle
658659
onMounted(async () => {
659660
window.addEventListener("keydown", handleKeyDown);
661+
window.addEventListener("keyup", handleKeyUp);
660662
661663
// Clear any stale error/connection state from previous navigations
662664
if (typeof meetingState.resetConnectionState === "function") {
@@ -736,6 +738,7 @@ onMounted(async () => {
736738
737739
onUnmounted(() => {
738740
window.removeEventListener("keydown", handleKeyDown);
741+
window.removeEventListener("keyup", handleKeyUp);
739742
740743
// Cleanup will be handled by the meeting logic composable
741744
});

0 commit comments

Comments
 (0)