feat: allow to play sound twice on dfferent device

This commit is contained in:
HerozDotExe 2025-09-21 19:23:56 +02:00
parent 2af92f8e16
commit 1de7e5ccce
4 changed files with 147 additions and 95 deletions

View file

@ -1,21 +1,29 @@
export class _Audio {
src: string
audio: HTMLAudioElement
secondaryAudio: HTMLAudioElement
constructor(src: string) {
this.src = src
this.audio = new Audio(src)
this.secondaryAudio = new Audio(src)
}
play() {
this.audio.play()
this.secondaryAudio.play()
}
stop() {
this.audio.pause()
this.secondaryAudio.pause()
this.audio.remove()
this.secondaryAudio.remove()
}
setOuputDevice(id: string) {
setMainOuputDevice(id: string) {
this.audio.setSinkId(id)
}
setSecondaryOuputDevice(id: string) {
this.secondaryAudio.setSinkId(id)
}
}

View file

@ -0,0 +1,130 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog/'
import { Button } from '$lib/components/ui/button'
import * as Select from '$lib/components/ui/select'
import * as RadioGroup from '$lib/components/ui/radio-group/'
import { Label } from '$lib/components/ui/label/'
import { onMount } from 'svelte'
let showSelectDeviceDialog = $state(false)
let devices: MediaDeviceInfo[] = $state([])
let mainDevice = $state(localStorage.getItem('output_main_device_id'))
let secondaryDevice = $state(localStorage.getItem('output_secondary_device_id'))
const mainTriggerContent: string = $derived(
devices.find((d) => d.deviceId === mainDevice)?.label ?? 'Select a device'
)
const secondaryTriggerContent: string = $derived(
devices.find((d) => d.deviceId === secondaryDevice)?.label ?? 'Select a device'
)
let kinds = $state([])
let mainKind = $state('audiooutput')
let secondaryKind = $state('audiooutput')
async function updateDevices(): Promise<void> {
devices = await navigator.mediaDevices.enumerateDevices()
kinds = []
devices.forEach((d) => {
if (!kinds.includes(d.kind)) {
kinds.push(d.kind)
}
})
devices = devices.filter((d) => d.kind === mainKind)
}
onMount(() => {
updateDevices()
console.log(devices)
if (!localStorage.getItem('output_main_device_id')) {
showSelectDeviceDialog = true
}
})
</script>
<Dialog.Root open={showSelectDeviceDialog}>
<Dialog.Trigger
><Button
class="absolute top-[50%] right-8 w-fit translate-y-[-50%]"
onclick={() => {
showSelectDeviceDialog = true
}}>Select audio devices</Button
></Dialog.Trigger>
<Dialog.Content class="max-w-[425px]">
<Dialog.Header>
<Dialog.Title>Select main audio device</Dialog.Title>
<Dialog.Description>Usually you should select "CABLE Input"</Dialog.Description>
</Dialog.Header>
<RadioGroup.Root bind:value={mainKind} onValueChange={updateDevices}>
{#each kinds as kind (kind)}
<div class="flex items-center space-x-2">
<RadioGroup.Item value={kind} />
<Label for="r1">{kind}</Label>
</div>
{/each}
</RadioGroup.Root>
<Select.Root type="single" name="device" bind:value={mainDevice}>
<Select.Trigger class="w-full">
{mainTriggerContent}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Devices</Select.Label>
{#each devices as device (`${device.deviceId}`)}
<Select.Item value={device.deviceId} label={device.label}>
{device.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
<br />
<Dialog.Header>
<Dialog.Title>Select secondary audio device</Dialog.Title>
<Dialog.Description>Usually you should your headset</Dialog.Description>
</Dialog.Header>
<RadioGroup.Root bind:value={secondaryKind} onValueChange={updateDevices}>
{#each kinds as secondaryKind (secondaryKind)}
<div class="flex items-center space-x-2">
<RadioGroup.Item value={secondaryKind} />
<Label for="r1">{secondaryKind}</Label>
</div>
{/each}
</RadioGroup.Root>
<Select.Root type="single" name="device" bind:value={secondaryDevice}>
<Select.Trigger class="w-full">
{secondaryTriggerContent}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Devices</Select.Label>
{#each devices as device (`${device.deviceId}`)}
<Select.Item value={device.deviceId} label={device.label}>
{device.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
<Dialog.Footer>
<Button
type="button"
class="w-full"
onclick={() => {
console.log('save')
if (mainDevice !== '') {
localStorage.setItem('output_main_device_id', mainDevice)
} else localStorage.removeItem('output_main_device_id')
if (secondaryDevice !== '') {
localStorage.setItem('output_secondary_device_id', secondaryDevice)
} else localStorage.removeItem('output_secondary_device_id')
showSelectDeviceDialog = false
}}>Save</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>

View file

@ -1,95 +1,7 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog/'
import { Button } from '$lib/components/ui/button'
import * as Select from '$lib/components/ui/select'
import * as RadioGroup from '$lib/components/ui/radio-group/'
import { Label } from '$lib/components/ui/label/'
import { onMount } from 'svelte'
let showSelectDeviceDialog = $state(false)
let devices: MediaDeviceInfo[] = $state([])
let device = $state(localStorage.getItem('output_device_id'))
const triggerContent: string = $derived(
devices.find((d) => d.deviceId === device)?.label ?? 'Select a device'
)
let kinds = $state([])
let kind = $state('audiooutput')
async function updateDevices(): Promise<void> {
devices = await navigator.mediaDevices.enumerateDevices()
kinds = []
devices.forEach((d) => {
if (!kinds.includes(d.kind)) {
kinds.push(d.kind)
}
})
devices = devices.filter((d) => d.kind === kind)
}
onMount(() => {
updateDevices()
console.log(devices)
if (!localStorage.getItem('output_device_id')) {
showSelectDeviceDialog = true
}
})
<script>
import DevicesSelector from './DevicesSelector.svelte'
</script>
<footer class="absolute bottom-0 left-0 h-16 w-full">
<Dialog.Root open={showSelectDeviceDialog}>
<Dialog.Trigger
><Button
class="absolute top-[50%] right-8 w-fit translate-y-[-50%]"
onclick={() => {
showSelectDeviceDialog = true
}}>Select audio device</Button
></Dialog.Trigger>
<Dialog.Content class="max-w-[425px]">
<Dialog.Header>
<Dialog.Title>Select audio device</Dialog.Title>
<Dialog.Description>Usually you should select "CABLE Input"</Dialog.Description>
</Dialog.Header>
<RadioGroup.Root bind:value={kind} onValueChange={updateDevices}>
{#each kinds as kind (kind)}
<div class="flex items-center space-x-2">
<RadioGroup.Item value={kind} />
<Label for="r1">{kind}</Label>
</div>
{/each}
</RadioGroup.Root>
<Select.Root type="single" name="device" bind:value={device}>
<Select.Trigger class="w-full">
{triggerContent}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Devices</Select.Label>
{#each devices as device (`${device.deviceId}`)}
<Select.Item value={device.deviceId} label={device.label}>
{device.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
<Dialog.Footer>
<Button
type="button"
class="w-full"
onclick={() => {
console.log('save')
if (device !== '') {
localStorage.setItem('output_device_id', device)
} else localStorage.removeItem('output_device_id')
showSelectDeviceDialog = false
}}>Save</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<DevicesSelector></DevicesSelector>
</footer>

View file

@ -7,17 +7,19 @@
let sound: _Audio
function play(src: string): void {
if (!localStorage.getItem('output_device_id')) {
if (!localStorage.getItem('output_main_device_id')) {
return alert('Please select an audio device to ouput sounds.')
}
if (sound) {
sound.stop()
}
sound = new _Audio(src)
sound.setOuputDevice(localStorage.getItem('output_device_id'))
sound.setMainOuputDevice(localStorage.getItem('output_main_device_id'))
if (localStorage.getItem('output_secondary_device_id')) {
sound.setSecondaryOuputDevice(localStorage.getItem('output_secondary_device_id'))
}
sound.play()
}
onMount(async () => {
states.sounds = await window.api.search()
})