mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on external audio sources, started adding generic audio packet handling
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { FaQuestionCircle } from "react-icons/fa";
|
||||
import { AudioSourcePanel } from "./components/audiosourcepanel";
|
||||
import { AudioSourcePanel } from "./components/sourcepanel";
|
||||
import { AudioSource } from "../../audio/audiosource";
|
||||
|
||||
export function AudioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../../components/olstatebutton";
|
||||
import { faPause, faPlay, faRepeat, faStop } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../../olympusapp";
|
||||
import { AudioSource } from "../../../audio/audiosource";
|
||||
import { FaTrash, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { OlRangeSlider } from "../../components/olrangeslider";
|
||||
import { FaUnlink } from "react-icons/fa";
|
||||
import { OlDropdown, OlDropdownItem } from "../../components/oldropdown";
|
||||
import { FileSource } from "../../../audio/filesource";
|
||||
|
||||
export function AudioSourcePanel(props: { source: AudioSource }) {
|
||||
const [meterLevel, setMeterLevel] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
// TODO apply to all sources
|
||||
if (props.source instanceof FileSource) {
|
||||
setMeterLevel(props.source.getMeter().getPeaks().current[0]);
|
||||
}
|
||||
}, 50);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-between gap-2 rounded-md
|
||||
bg-olympus-200/30 py-3 pl-4 pr-5
|
||||
`}
|
||||
>
|
||||
<span>{props.source.getName()}</span>
|
||||
{props.source instanceof FileSource && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-4">
|
||||
<OlStateButton
|
||||
checked={false}
|
||||
icon={props.source.getPlaying() ? faPause : faPlay}
|
||||
onClick={() => {
|
||||
if (props.source instanceof FileSource) props.source.getPlaying() ? props.source.stop() : props.source.play();
|
||||
}}
|
||||
tooltip="Play file"
|
||||
></OlStateButton>
|
||||
<OlRangeSlider
|
||||
value={(props.source.getCurrentPosition() / props.source.getDuration()) * 100}
|
||||
onChange={(ev) => {
|
||||
if (props.source instanceof FileSource) props.source.setCurrentPosition(parseFloat(ev.currentTarget.value));
|
||||
}}
|
||||
className="my-auto"
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={props.source.getLooping()}
|
||||
icon={faRepeat}
|
||||
onClick={() => {
|
||||
if (props.source instanceof FileSource) props.source.setLooping(!props.source.getLooping());
|
||||
}}
|
||||
tooltip="Loop"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[40px] min-w-[40px] p-2">
|
||||
<FaVolumeHigh className="h-full w-full" />
|
||||
</div>
|
||||
<div className="relative flex w-full flex-col gap-3">
|
||||
<div
|
||||
className={`
|
||||
absolute top-[18px] flex h-2 min-w-full translate-y-[-5px]
|
||||
flex-row border-gray-500
|
||||
`}
|
||||
>
|
||||
<div
|
||||
style={{ minWidth: `${meterLevel * 100}%` }}
|
||||
className={`rounded-full bg-gray-200`}
|
||||
></div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={props.source.getVolume() * 100}
|
||||
onChange={(ev) => {
|
||||
if (props.source instanceof FileSource) props.source.setVolume(parseFloat(ev.currentTarget.value) / 100);
|
||||
}}
|
||||
className="absolute top-[18px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="h-[40px] min-w-[40px] p-2">
|
||||
<span>{Math.round(props.source.getVolume() * 100)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm">Connected to:</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
{props.source.getConnectedTo().map((sink) => {
|
||||
return (
|
||||
<div className="flex justify-between text-sm">
|
||||
{sink.getName()}
|
||||
<FaUnlink className="cursor-pointer" onClick={() => props.source.disconnect(sink)}></FaUnlink>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<OlDropdown label="Connect to:">
|
||||
{getApp()
|
||||
.getAudioManager()
|
||||
.getSinks()
|
||||
.filter((sink) => !props.source.getConnectedTo().includes(sink))
|
||||
.map((sink) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
props.source.connect(sink);
|
||||
}}
|
||||
>
|
||||
{sink.getName()}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function RadioPanel(props: { radio: RadioSink }) {
|
||||
>
|
||||
<div className="flex content-center justify-between">
|
||||
<span className="my-auto">{props.radio.getName()}</span>
|
||||
<div className="rounded-md bg-red-800 p-2" onClick={() => {getApp().getAudioManager().removeSink(props.radio);}}>
|
||||
<div className="cursor-pointer rounded-md bg-red-800 p-2" onClick={() => {getApp().getAudioManager().removeSink(props.radio);}}>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
141
frontend/react/src/ui/panels/components/sourcepanel.tsx
Normal file
141
frontend/react/src/ui/panels/components/sourcepanel.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../../components/olstatebutton";
|
||||
import { faPause, faPlay, faRepeat, faStop } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../../olympusapp";
|
||||
import { AudioSource } from "../../../audio/audiosource";
|
||||
import { FaArrowRight, FaTrash, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { OlRangeSlider } from "../../components/olrangeslider";
|
||||
import { FaUnlink } from "react-icons/fa";
|
||||
import { OlDropdown, OlDropdownItem } from "../../components/oldropdown";
|
||||
import { FileSource } from "../../../audio/filesource";
|
||||
import { MicrophoneSource } from "../../../audio/microphonesource";
|
||||
|
||||
export function AudioSourcePanel(props: { source: AudioSource }) {
|
||||
const [meterLevel, setMeterLevel] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
setMeterLevel(props.source.getMeter().getPeaks().current[0]);
|
||||
}, 50);
|
||||
}, []);
|
||||
|
||||
let availabileSinks = getApp()
|
||||
.getAudioManager()
|
||||
.getSinks()
|
||||
.filter((sink) => !props.source.getConnectedTo().includes(sink));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-between gap-2 rounded-md
|
||||
bg-olympus-200/30 py-3 pl-4 pr-5
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="break-all">{props.source.getName()}</span>
|
||||
{!(props.source instanceof MicrophoneSource) && (
|
||||
<div
|
||||
className={`
|
||||
mb-auto aspect-square cursor-pointer rounded-md bg-red-800 p-2
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp().getAudioManager().removeSource(props.source);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 rounded-md bg-olympus-400 p-2">
|
||||
{props.source instanceof FileSource && (
|
||||
<div className="flex gap-4">
|
||||
<OlStateButton
|
||||
checked={false}
|
||||
icon={props.source.getPlaying() ? faPause : faPlay}
|
||||
onClick={() => {
|
||||
if (props.source instanceof FileSource) props.source.getPlaying() ? props.source.stop() : props.source.play();
|
||||
}}
|
||||
tooltip="Play file"
|
||||
></OlStateButton>
|
||||
<OlRangeSlider
|
||||
value={props.source.getDuration() > 0 ? (props.source.getCurrentPosition() / props.source.getDuration()) * 100 : 0}
|
||||
onChange={(ev) => {
|
||||
if (props.source instanceof FileSource) props.source.setCurrentPosition(parseFloat(ev.currentTarget.value));
|
||||
}}
|
||||
className="my-auto"
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={props.source.getLooping()}
|
||||
icon={faRepeat}
|
||||
onClick={() => {
|
||||
if (props.source instanceof FileSource) props.source.setLooping(!props.source.getLooping());
|
||||
}}
|
||||
tooltip="Loop"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[40px] min-w-[40px] p-2">
|
||||
<FaVolumeHigh className="h-full w-full" />
|
||||
</div>
|
||||
<div className="relative flex w-full flex-col gap-3">
|
||||
<div
|
||||
className={`
|
||||
absolute top-[18px] flex h-2 min-w-full translate-y-[-5px]
|
||||
flex-row border-gray-500
|
||||
`}
|
||||
>
|
||||
<div style={{ minWidth: `${meterLevel * 100}%` }} className={`
|
||||
rounded-full bg-gray-200
|
||||
`}></div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={props.source.getVolume() * 100}
|
||||
onChange={(ev) => {
|
||||
props.source.setVolume(parseFloat(ev.currentTarget.value) / 100);
|
||||
}}
|
||||
className="absolute top-[18px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="h-[40px] min-w-[40px] p-2">
|
||||
<span>{Math.round(props.source.getVolume() * 100)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-sm">Connected to:</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
{props.source.getConnectedTo().map((sink) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex justify-start gap-2 rounded-full bg-olympus-400 px-4 py-1
|
||||
text-sm
|
||||
`}
|
||||
>
|
||||
<FaArrowRight className="my-auto"></FaArrowRight>
|
||||
{sink.getName()}
|
||||
<FaUnlink className="my-auto ml-auto cursor-pointer text-red-400" onClick={() => props.source.disconnect(sink)}></FaUnlink>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{availabileSinks.length > 0 && (
|
||||
<OlDropdown label="Connect to:">
|
||||
{availabileSinks.map((sink) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
props.source.connect(sink);
|
||||
}}
|
||||
>
|
||||
{sink.getName()}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -134,14 +134,6 @@ export function UI() {
|
||||
setLoginModalVisible(false);
|
||||
}
|
||||
|
||||
/* Temporary during devel */
|
||||
//useEffect(() => {
|
||||
// window.setTimeout(() => {
|
||||
// checkPassword("admin");
|
||||
// connect("devel");
|
||||
// }, 1000)
|
||||
//}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
|
||||
Reference in New Issue
Block a user