feat: allow to play sound twice on dfferent device
This commit is contained in:
parent
2af92f8e16
commit
1de7e5ccce
4 changed files with 147 additions and 95 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
src/renderer/src/lib/components/DevicesSelector.svelte
Normal file
130
src/renderer/src/lib/components/DevicesSelector.svelte
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue