Compare commits
2 Commits
8265e2d719
...
43a8b98a6a
| Author | SHA1 | Date | |
|---|---|---|---|
| 43a8b98a6a | |||
| 0f9a7e39ce |
1
client/app/components.d.ts
vendored
1
client/app/components.d.ts
vendored
@@ -21,6 +21,7 @@ declare module 'vue' {
|
|||||||
PrimePanel: typeof import('primevue/panel')['default']
|
PrimePanel: typeof import('primevue/panel')['default']
|
||||||
PrimePassword: typeof import('primevue/password')['default']
|
PrimePassword: typeof import('primevue/password')['default']
|
||||||
PrimeProgressBar: typeof import('primevue/progressbar')['default']
|
PrimeProgressBar: typeof import('primevue/progressbar')['default']
|
||||||
|
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
|
||||||
PrimeSelect: typeof import('primevue/select')['default']
|
PrimeSelect: typeof import('primevue/select')['default']
|
||||||
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
const ctx = new (window.AudioContext || window.webkitAudioContext)()
|
const ctx = new (window.AudioContext || window.webkitAudioContext)()
|
||||||
|
|
||||||
const stream = new MediaStream()
|
const stream = new MediaStream()
|
||||||
|
const audioEl = new Audio()
|
||||||
|
|
||||||
const sourceNode = shallowRef<MediaStreamAudioSourceNode>()
|
const sourceNode = shallowRef<MediaStreamAudioSourceNode>()
|
||||||
const gainNode = ctx.createGain()
|
const gainNode = ctx.createGain()
|
||||||
|
|
||||||
let hackExecuted = false
|
watch(audioTrack, async (track, prevTrack) => {
|
||||||
|
|
||||||
watch(audioTrack, (track, prevTrack) => {
|
|
||||||
if (prevTrack)
|
if (prevTrack)
|
||||||
stream.removeTrack(prevTrack)
|
stream.removeTrack(prevTrack)
|
||||||
|
|
||||||
@@ -19,16 +18,14 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
|
|
||||||
stream.addTrack(track)
|
stream.addTrack(track)
|
||||||
|
|
||||||
if (!hackExecuted) {
|
if (!audioEl.srcObject) {
|
||||||
const audioEl = new Audio()
|
|
||||||
audioEl.srcObject = stream
|
audioEl.srcObject = stream
|
||||||
audioEl.muted = true
|
audioEl.muted = true
|
||||||
hackExecuted = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceNode.value = ctx.createMediaStreamSource(stream)
|
sourceNode.value = ctx.createMediaStreamSource(stream)
|
||||||
|
|
||||||
connect()
|
await connect()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
useEventListener(document, 'click', async () => {
|
useEventListener(document, 'click', async () => {
|
||||||
@@ -36,10 +33,16 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
await ctx.resume()
|
await ctx.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
connect()
|
await connect()
|
||||||
}, { once: true })
|
}, { once: true })
|
||||||
|
|
||||||
function connect() {
|
onScopeDispose(() => {
|
||||||
|
audioEl.pause()
|
||||||
|
audioEl.srcObject = null
|
||||||
|
ctx.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function connect() {
|
||||||
if (!sourceNode.value || ctx.state === 'suspended')
|
if (!sourceNode.value || ctx.state === 'suspended')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -165,8 +165,6 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
if (producerPaused)
|
if (producerPaused)
|
||||||
consumer.pause()
|
consumer.pause()
|
||||||
|
|
||||||
console.log('newConsumer', consumer.paused)
|
|
||||||
|
|
||||||
consumer.on('transportclose', () => {
|
consumer.on('transportclose', () => {
|
||||||
if (consumers.value.delete(consumer.id))
|
if (consumers.value.delete(consumer.id))
|
||||||
triggerRef(consumers)
|
triggerRef(consumers)
|
||||||
@@ -248,12 +246,13 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
autoGainControl: false,
|
deviceId: { exact: preferences.inputDeviceId.value },
|
||||||
noiseSuppression: true,
|
autoGainControl: { exact: preferences.autoGainControl.value },
|
||||||
echoCancellation: false,
|
echoCancellation: { exact: preferences.echoCancellation.value },
|
||||||
channelCount: 2,
|
noiseSuppression: { exact: preferences.noiseSuppression.value },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const track = stream.getAudioTracks()[0]
|
const track = stream.getAudioTracks()[0]
|
||||||
|
|
||||||
if (!track)
|
if (!track)
|
||||||
@@ -270,6 +269,7 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
|
|
||||||
producers.value.set(micProducer.value.id, micProducer.value)
|
producers.value.set(micProducer.value.id, micProducer.value)
|
||||||
triggerRef(producers)
|
triggerRef(producers)
|
||||||
|
triggerRef(micProducer)
|
||||||
|
|
||||||
micProducer.value.on('transportclose', () => {
|
micProducer.value.on('transportclose', () => {
|
||||||
micProducer.value = undefined
|
micProducer.value = undefined
|
||||||
@@ -297,6 +297,7 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
triggerRef(producers)
|
triggerRef(producers)
|
||||||
|
triggerRef(micProducer)
|
||||||
}
|
}
|
||||||
|
|
||||||
micProducer.value = undefined
|
micProducer.value = undefined
|
||||||
@@ -370,6 +371,32 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
preferences.inputDeviceId,
|
||||||
|
async (inputDeviceId) => {
|
||||||
|
await disableMic()
|
||||||
|
|
||||||
|
if (!inputDeviceId)
|
||||||
|
return
|
||||||
|
|
||||||
|
await enableMic()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
watch([
|
||||||
|
preferences.inputDeviceId,
|
||||||
|
preferences.echoCancellation,
|
||||||
|
preferences.autoGainControl,
|
||||||
|
preferences.noiseSuppression,
|
||||||
|
], async ([inputDeviceId]) => {
|
||||||
|
await disableMic()
|
||||||
|
|
||||||
|
if (!inputDeviceId)
|
||||||
|
return
|
||||||
|
|
||||||
|
await enableMic()
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init,
|
init,
|
||||||
consumers,
|
consumers,
|
||||||
|
|||||||
@@ -16,14 +16,24 @@ export const usePreferences = createGlobalState(() => {
|
|||||||
audioOutputs,
|
audioOutputs,
|
||||||
} = useDevicesList()
|
} = useDevicesList()
|
||||||
|
|
||||||
|
const inputDeviceExist = computed(() => {
|
||||||
|
return audioInputs.value.some(device => device.deviceId === inputDeviceId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const outputDeviceExist = computed(() => {
|
||||||
|
return audioOutputs.value.some(device => device.deviceId === outputDeviceId.value)
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputDeviceId,
|
inputDeviceId,
|
||||||
outputDeviceId,
|
outputDeviceId,
|
||||||
autoGainControl,
|
autoGainControl,
|
||||||
noiseSuppression,
|
noiseSuppression,
|
||||||
echoCancellation,
|
echoCancellation,
|
||||||
videoInputs,
|
inputDeviceExist,
|
||||||
audioInputs,
|
outputDeviceExist,
|
||||||
audioOutputs,
|
videoInputs: computed(() => JSON.parse(JSON.stringify(videoInputs.value))),
|
||||||
|
audioInputs: computed(() => JSON.parse(JSON.stringify(audioInputs.value))),
|
||||||
|
audioOutputs: computed(() => JSON.parse(JSON.stringify(audioOutputs.value))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-2 h-screen">
|
<div class="grid grid-cols-2 gap-2 p-2 h-screen grid-rows-[auto_1fr]">
|
||||||
<div class="flex flex-col shadow-xl shadow-surface-950 overflow-y-hidden">
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between gap-2 border-b-2 border-surface-800 px-3 py-3 bg-surface-950"
|
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
||||||
style="height: 75px;"
|
|
||||||
>
|
>
|
||||||
<div class="inline-flex items-center gap-3">
|
<div class="inline-flex items-center gap-3">
|
||||||
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" />
|
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" />
|
||||||
@@ -24,15 +22,8 @@
|
|||||||
</PrimeButtonGroup>
|
</PrimeButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-auto-animate class="p-3 overflow-y-auto flex-1 bg-surface-900 overflow-hidden divide-y divide-surface-800">
|
|
||||||
<ClientRow v-for="client of clients" :key="client.id" :client="client" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-surface-950 overflow-y-auto">
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-center gap-2 border-b-2 border-surface-800 px-3 py-3 bg-surface-900"
|
class="flex items-center justify-center rounded-xl p-3 bg-surface-950"
|
||||||
style="height: 75px;"
|
|
||||||
>
|
>
|
||||||
<PrimeSelectButton
|
<PrimeSelectButton
|
||||||
v-model="activeTab"
|
v-model="activeTab"
|
||||||
@@ -47,8 +38,17 @@
|
|||||||
</PrimeSelectButton>
|
</PrimeSelectButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
|
||||||
|
<div v-auto-animate class="p-3 divide-y divide-surface-800">
|
||||||
|
<ClientRow v-for="client of clients" :key="client.id" :client="client" />
|
||||||
|
</div>
|
||||||
|
</PrimeScrollPanel>
|
||||||
|
|
||||||
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
|
||||||
|
<div class="p-3">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
</PrimeScrollPanel>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-center p-3">
|
<div class="flex items-center justify-center">
|
||||||
<PrimeCard>
|
<PrimeCard>
|
||||||
<template #content>
|
<template #content>
|
||||||
The chat is under development.
|
The chat is under development.
|
||||||
@@ -9,9 +9,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { clients } = useClients()
|
|
||||||
const { producers, consumers } = useMediasoup()
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Index',
|
name: 'Index',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="px-3 py-6">
|
<div>
|
||||||
<PrimeFloatLabel variant="on">
|
<PrimeFloatLabel variant="on">
|
||||||
<PrimeSelect
|
<PrimeSelect
|
||||||
v-model="inputDeviceId"
|
v-model="inputDeviceId"
|
||||||
:options="audioInputsHack"
|
:options="audioInputs"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-value="deviceId"
|
option-value="deviceId"
|
||||||
fluid
|
fluid
|
||||||
input-id="inputDevice"
|
input-id="inputDevice"
|
||||||
|
:invalid="!inputDeviceExist"
|
||||||
/>
|
/>
|
||||||
<label for="inputDevice">Input device</label>
|
<label for="inputDevice">Input device</label>
|
||||||
</PrimeFloatLabel>
|
</PrimeFloatLabel>
|
||||||
@@ -27,23 +28,25 @@
|
|||||||
<label for="noiseSuppression">Noise Suppression</label>
|
<label for="noiseSuppression">Noise Suppression</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimeFloatLabel variant="on">
|
<!-- <PrimeFloatLabel variant="on"> -->
|
||||||
<PrimeSelect
|
<!-- <PrimeSelect -->
|
||||||
v-model="outputDeviceId"
|
<!-- v-model="outputDeviceId" -->
|
||||||
:options="audioOutputsHack"
|
<!-- :options="audioOutputs" -->
|
||||||
option-label="label"
|
<!-- option-label="label" -->
|
||||||
option-value="deviceId"
|
<!-- option-value="deviceId" -->
|
||||||
fluid
|
<!-- fluid -->
|
||||||
class="mt-6"
|
<!-- class="mt-6" -->
|
||||||
input-id="outputDevice"
|
<!-- input-id="outputDevice" -->
|
||||||
/>
|
<!-- :invalid="!outputDeviceExist" -->
|
||||||
<label for="outputDevice">Output device</label>
|
<!-- -->
|
||||||
</PrimeFloatLabel>
|
<!-- /> -->
|
||||||
|
<!-- <label for="outputDevice">Output device</label> -->
|
||||||
|
<!-- </PrimeFloatLabel> -->
|
||||||
|
|
||||||
|
<template v-if="isTauri">
|
||||||
<PrimeDivider />
|
<PrimeDivider />
|
||||||
|
|
||||||
<PrimeButton
|
<PrimeButton
|
||||||
v-if="isTauri"
|
|
||||||
size="small"
|
size="small"
|
||||||
label="Check for Updates"
|
label="Check for Updates"
|
||||||
fluid
|
fluid
|
||||||
@@ -51,6 +54,7 @@
|
|||||||
:loading="checking"
|
:loading="checking"
|
||||||
@click="onCheckForUpdates"
|
@click="onCheckForUpdates"
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimeToast position="bottom-center" group="updater">
|
<PrimeToast position="bottom-center" group="updater">
|
||||||
@@ -80,19 +84,14 @@ const {
|
|||||||
autoGainControl,
|
autoGainControl,
|
||||||
noiseSuppression,
|
noiseSuppression,
|
||||||
echoCancellation,
|
echoCancellation,
|
||||||
|
inputDeviceExist,
|
||||||
|
outputDeviceExist,
|
||||||
audioInputs,
|
audioInputs,
|
||||||
audioOutputs,
|
audioOutputs,
|
||||||
} = usePreferences()
|
} = usePreferences()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const audioInputsHack = computed(() => {
|
|
||||||
return JSON.parse(JSON.stringify(audioInputs.value))
|
|
||||||
})
|
|
||||||
const audioOutputsHack = computed(() => {
|
|
||||||
return JSON.parse(JSON.stringify(audioOutputs.value))
|
|
||||||
})
|
|
||||||
|
|
||||||
async function onCheckForUpdates() {
|
async function onCheckForUpdates() {
|
||||||
const update = await checkForUpdates()
|
const update = await checkForUpdates()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="px-3 py-6" @submit.prevent="save()">
|
<form @submit.prevent="save()">
|
||||||
<PrimeFloatLabel variant="on">
|
<PrimeFloatLabel variant="on">
|
||||||
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
||||||
<label for="displayName">Display name</label>
|
<label for="displayName">Display name</label>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "chad",
|
"productName": "chad",
|
||||||
"version": "0.2.11",
|
"version": "0.2.12",
|
||||||
"identifier": "xyz.koptilnya.chad",
|
"identifier": "xyz.koptilnya.chad",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../.output/public",
|
"frontendDist": "../.output/public",
|
||||||
|
|||||||
Reference in New Issue
Block a user