Compare commits
2 Commits
8265e2d719
...
v0.2.12
| 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']
|
||||
PrimePassword: typeof import('primevue/password')['default']
|
||||
PrimeProgressBar: typeof import('primevue/progressbar')['default']
|
||||
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
|
||||
PrimeSelect: typeof import('primevue/select')['default']
|
||||
PrimeSelectButton: typeof import('primevue/selectbutton')['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 stream = new MediaStream()
|
||||
const audioEl = new Audio()
|
||||
|
||||
const sourceNode = shallowRef<MediaStreamAudioSourceNode>()
|
||||
const gainNode = ctx.createGain()
|
||||
|
||||
let hackExecuted = false
|
||||
|
||||
watch(audioTrack, (track, prevTrack) => {
|
||||
watch(audioTrack, async (track, prevTrack) => {
|
||||
if (prevTrack)
|
||||
stream.removeTrack(prevTrack)
|
||||
|
||||
@@ -19,16 +18,14 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
||||
|
||||
stream.addTrack(track)
|
||||
|
||||
if (!hackExecuted) {
|
||||
const audioEl = new Audio()
|
||||
if (!audioEl.srcObject) {
|
||||
audioEl.srcObject = stream
|
||||
audioEl.muted = true
|
||||
hackExecuted = true
|
||||
}
|
||||
|
||||
sourceNode.value = ctx.createMediaStreamSource(stream)
|
||||
|
||||
connect()
|
||||
await connect()
|
||||
}, { immediate: true })
|
||||
|
||||
useEventListener(document, 'click', async () => {
|
||||
@@ -36,10 +33,16 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
||||
await ctx.resume()
|
||||
}
|
||||
|
||||
connect()
|
||||
await connect()
|
||||
}, { once: true })
|
||||
|
||||
function connect() {
|
||||
onScopeDispose(() => {
|
||||
audioEl.pause()
|
||||
audioEl.srcObject = null
|
||||
ctx.close()
|
||||
})
|
||||
|
||||
async function connect() {
|
||||
if (!sourceNode.value || ctx.state === 'suspended')
|
||||
return
|
||||
|
||||
|
||||
@@ -165,8 +165,6 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
if (producerPaused)
|
||||
consumer.pause()
|
||||
|
||||
console.log('newConsumer', consumer.paused)
|
||||
|
||||
consumer.on('transportclose', () => {
|
||||
if (consumers.value.delete(consumer.id))
|
||||
triggerRef(consumers)
|
||||
@@ -248,12 +246,13 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
autoGainControl: false,
|
||||
noiseSuppression: true,
|
||||
echoCancellation: false,
|
||||
channelCount: 2,
|
||||
deviceId: { exact: preferences.inputDeviceId.value },
|
||||
autoGainControl: { exact: preferences.autoGainControl.value },
|
||||
echoCancellation: { exact: preferences.echoCancellation.value },
|
||||
noiseSuppression: { exact: preferences.noiseSuppression.value },
|
||||
},
|
||||
})
|
||||
|
||||
const track = stream.getAudioTracks()[0]
|
||||
|
||||
if (!track)
|
||||
@@ -270,6 +269,7 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
|
||||
producers.value.set(micProducer.value.id, micProducer.value)
|
||||
triggerRef(producers)
|
||||
triggerRef(micProducer)
|
||||
|
||||
micProducer.value.on('transportclose', () => {
|
||||
micProducer.value = undefined
|
||||
@@ -297,6 +297,7 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
}
|
||||
finally {
|
||||
triggerRef(producers)
|
||||
triggerRef(micProducer)
|
||||
}
|
||||
|
||||
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 {
|
||||
init,
|
||||
consumers,
|
||||
|
||||
@@ -16,14 +16,24 @@ export const usePreferences = createGlobalState(() => {
|
||||
audioOutputs,
|
||||
} = 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 {
|
||||
inputDeviceId,
|
||||
outputDeviceId,
|
||||
autoGainControl,
|
||||
noiseSuppression,
|
||||
echoCancellation,
|
||||
videoInputs,
|
||||
audioInputs,
|
||||
audioOutputs,
|
||||
inputDeviceExist,
|
||||
outputDeviceExist,
|
||||
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,54 +1,54 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 h-screen">
|
||||
<div class="flex flex-col shadow-xl shadow-surface-950 overflow-y-hidden">
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 border-b-2 border-surface-800 px-3 py-3 bg-surface-950"
|
||||
style="height: 75px;"
|
||||
>
|
||||
<div class="inline-flex items-center gap-3">
|
||||
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" />
|
||||
<PrimeBadge :severity="connected ? 'success' : 'danger' " />
|
||||
</div>
|
||||
|
||||
<PrimeButtonGroup class="ml-auto">
|
||||
<PrimeButton outlined @click="toggleInput">
|
||||
<template #icon>
|
||||
<Component :is="inputMuted ? MicOff : Mic" />
|
||||
</template>
|
||||
</PrimeButton>
|
||||
<PrimeButton outlined @click="toggleOutput">
|
||||
<template #icon>
|
||||
<Component :is="outputMuted ? VolumeOff : Volume2" />
|
||||
</template>
|
||||
</PrimeButton>
|
||||
</PrimeButtonGroup>
|
||||
<div class="grid grid-cols-2 gap-2 p-2 h-screen grid-rows-[auto_1fr]">
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
||||
>
|
||||
<div class="inline-flex items-center gap-3">
|
||||
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" />
|
||||
<PrimeBadge :severity="connected ? 'success' : 'danger' " />
|
||||
</div>
|
||||
|
||||
<div v-auto-animate class="p-3 overflow-y-auto flex-1 bg-surface-900 overflow-hidden divide-y divide-surface-800">
|
||||
<PrimeButtonGroup class="ml-auto">
|
||||
<PrimeButton outlined @click="toggleInput">
|
||||
<template #icon>
|
||||
<Component :is="inputMuted ? MicOff : Mic" />
|
||||
</template>
|
||||
</PrimeButton>
|
||||
<PrimeButton outlined @click="toggleOutput">
|
||||
<template #icon>
|
||||
<Component :is="outputMuted ? VolumeOff : Volume2" />
|
||||
</template>
|
||||
</PrimeButton>
|
||||
</PrimeButtonGroup>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-center rounded-xl p-3 bg-surface-950"
|
||||
>
|
||||
<PrimeSelectButton
|
||||
v-model="activeTab"
|
||||
:options="tabs"
|
||||
data-key="id"
|
||||
:allow-empty="false"
|
||||
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<Component :is="option.icon" size="24" />
|
||||
</template>
|
||||
</PrimeSelectButton>
|
||||
</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>
|
||||
</div>
|
||||
</PrimeScrollPanel>
|
||||
|
||||
<div class="bg-surface-950 overflow-y-auto">
|
||||
<div
|
||||
class="flex items-center justify-center gap-2 border-b-2 border-surface-800 px-3 py-3 bg-surface-900"
|
||||
style="height: 75px;"
|
||||
>
|
||||
<PrimeSelectButton
|
||||
v-model="activeTab"
|
||||
:options="tabs"
|
||||
data-key="id"
|
||||
:allow-empty="false"
|
||||
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<Component :is="option.icon" size="24" />
|
||||
</template>
|
||||
</PrimeSelectButton>
|
||||
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
|
||||
<div class="p-3">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</PrimeScrollPanel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center p-3">
|
||||
<div class="flex items-center justify-center">
|
||||
<PrimeCard>
|
||||
<template #content>
|
||||
The chat is under development.
|
||||
@@ -9,9 +9,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { clients } = useClients()
|
||||
const { producers, consumers } = useMediasoup()
|
||||
|
||||
definePageMeta({
|
||||
name: 'Index',
|
||||
})
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="px-3 py-6">
|
||||
<div>
|
||||
<PrimeFloatLabel variant="on">
|
||||
<PrimeSelect
|
||||
v-model="inputDeviceId"
|
||||
:options="audioInputsHack"
|
||||
:options="audioInputs"
|
||||
option-label="label"
|
||||
option-value="deviceId"
|
||||
fluid
|
||||
input-id="inputDevice"
|
||||
:invalid="!inputDeviceExist"
|
||||
/>
|
||||
<label for="inputDevice">Input device</label>
|
||||
</PrimeFloatLabel>
|
||||
@@ -27,30 +28,33 @@
|
||||
<label for="noiseSuppression">Noise Suppression</label>
|
||||
</div>
|
||||
|
||||
<PrimeFloatLabel variant="on">
|
||||
<PrimeSelect
|
||||
v-model="outputDeviceId"
|
||||
:options="audioOutputsHack"
|
||||
option-label="label"
|
||||
option-value="deviceId"
|
||||
<!-- <PrimeFloatLabel variant="on"> -->
|
||||
<!-- <PrimeSelect -->
|
||||
<!-- v-model="outputDeviceId" -->
|
||||
<!-- :options="audioOutputs" -->
|
||||
<!-- option-label="label" -->
|
||||
<!-- option-value="deviceId" -->
|
||||
<!-- fluid -->
|
||||
<!-- class="mt-6" -->
|
||||
<!-- input-id="outputDevice" -->
|
||||
<!-- :invalid="!outputDeviceExist" -->
|
||||
<!-- -->
|
||||
<!-- /> -->
|
||||
<!-- <label for="outputDevice">Output device</label> -->
|
||||
<!-- </PrimeFloatLabel> -->
|
||||
|
||||
<template v-if="isTauri">
|
||||
<PrimeDivider />
|
||||
|
||||
<PrimeButton
|
||||
size="small"
|
||||
label="Check for Updates"
|
||||
fluid
|
||||
class="mt-6"
|
||||
input-id="outputDevice"
|
||||
severity="info"
|
||||
:loading="checking"
|
||||
@click="onCheckForUpdates"
|
||||
/>
|
||||
<label for="outputDevice">Output device</label>
|
||||
</PrimeFloatLabel>
|
||||
|
||||
<PrimeDivider />
|
||||
|
||||
<PrimeButton
|
||||
v-if="isTauri"
|
||||
size="small"
|
||||
label="Check for Updates"
|
||||
fluid
|
||||
severity="info"
|
||||
:loading="checking"
|
||||
@click="onCheckForUpdates"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<PrimeToast position="bottom-center" group="updater">
|
||||
@@ -80,19 +84,14 @@ const {
|
||||
autoGainControl,
|
||||
noiseSuppression,
|
||||
echoCancellation,
|
||||
inputDeviceExist,
|
||||
outputDeviceExist,
|
||||
audioInputs,
|
||||
audioOutputs,
|
||||
} = usePreferences()
|
||||
|
||||
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() {
|
||||
const update = await checkForUpdates()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<form class="px-3 py-6" @submit.prevent="save()">
|
||||
<form @submit.prevent="save()">
|
||||
<PrimeFloatLabel variant="on">
|
||||
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
||||
<label for="displayName">Display name</label>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "chad",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"identifier": "xyz.koptilnya.chad",
|
||||
"build": {
|
||||
"frontendDist": "../.output/public",
|
||||
|
||||
Reference in New Issue
Block a user