Skip to content

Commit

Permalink
Feat(p-radio): Use icecast-metadata-player to play audio, update UI, …
Browse files Browse the repository at this point in the history
…added a bunch of other radio source
  • Loading branch information
Vija02 committed Dec 9, 2024
1 parent 7d3cf30 commit 68d0502
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 112 deletions.
2 changes: 2 additions & 0 deletions plugins/radio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"framer-motion": "^11.13.1",
"icecast-metadata-player": "^1.17.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"use-disposable": "^1.0.4",
"valtio": "^2.1.2",
"valtio-yjs": "^0.6.0",
"yjs": "^13.6.20"
Expand Down
12 changes: 12 additions & 0 deletions plugins/radio/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ObjectToTypedMap,
Plugin,
RegisterOnRendererDataCreated,
RegisterOnRendererDataLoaded,
ServerPluginApi,
TRPCObject,
Expand All @@ -21,6 +22,7 @@ export const init = (serverPluginApi: ServerPluginApi) => {
serverPluginApi.registerTrpcAppRouter(getAppRouter);
serverPluginApi.onPluginDataCreated(pluginName, onPluginDataCreated);
serverPluginApi.onPluginDataLoaded(pluginName, onPluginDataLoaded);
serverPluginApi.onRendererDataCreated(pluginName, onRendererDataCreated);
serverPluginApi.onRendererDataLoaded(pluginName, onRendererDataLoaded);
serverPluginApi.registerSceneCreator(pluginName, {
title: "Radio",
Expand Down Expand Up @@ -66,6 +68,16 @@ const onPluginDataLoaded = (
};
};

const onRendererDataCreated: RegisterOnRendererDataCreated<
PluginRendererData
> = (rendererData) => {
rendererData.set("url", null);
rendererData.set("isPlaying", false);
rendererData.set("volume", 1);

return {};
};

const onRendererDataLoaded: RegisterOnRendererDataLoaded<PluginRendererData> = (
rendererData,
) => {
Expand Down
1 change: 1 addition & 0 deletions plugins/radio/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type PluginBaseData = {
};

export type PluginRendererData = PluginRendererState & {
url: string | null;
isPlaying: boolean;
volume: number;
};
213 changes: 162 additions & 51 deletions plugins/radio/view/Remote/index.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,186 @@
import {
Box,
Button,
Heading,
Flex,
Link,
Slider,
SliderFilledTrack,
SliderMark,
SliderThumb,
SliderTrack,
Stack,
useBreakpointValue,
Text,
} from "@chakra-ui/react";
import { FaPause, FaPlay } from "react-icons/fa6";

import { usePluginAPI } from "../pluginApi";

const a = "https://s3.radio.co/sd16d576db/listen";
const b = "https://worship247.streamguys1.com/live-mp3-web";
const radios = [
{
title: "Worship 24/7 (worship247.com)",
webLink: "https://worship247.com",
coverUrl: "",
url: "https://worship247.streamguys1.com/live-aac",
},
{
title: "Worship Radio 247 (worshipradio247.org)",
webLink: "https://worshipradio247.org",
coverUrl: "",
url: "https://uk3-vn.mixstream.net/:8010/listen.mp3",
},
{
title: "AllWorship Christmas Worship",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice66.securenetsystems.net/AGCXW",
},
{
title: "AllWorship Instrumental Worship",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice66.securenetsystems.net/AGCIW",
},
{
title: "AllWorship Contemporary Worship",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice66.securenetsystems.net/AGCCW",
},
{
title: "AllWorship Praise & Worship",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice66.securenetsystems.net/AGCPW",
},
{
title: "AllWorship Gospel Worship",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice25.securenetsystems.net/AGCGW",
},
{
title: "AllWorship Hymns & Favorites",
webLink: "https://www.allworship.com",
coverUrl: "",
url: "https://ice25.securenetsystems.net/AGCHF",
},
{
title: "I Will Gather You Radio",
webLink: "https://worship247.com",
coverUrl: "",
url: "https://s3.radio.co/sd16d576db/listen",
},
{
title: "Power of Worship Radio",
webLink: "https://www.powerofworship.net",
coverUrl: "",
url: "https://stream.aiir.com/rwvbhh4xsgpuv",
},
];

const RadioRemote = () => {
const pluginApi = usePluginAPI();
const isPlaying = pluginApi.renderer.useData((x) => x.isPlaying);
const volume = pluginApi.renderer.useData((x) => x.volume);
const playingUrl = pluginApi.renderer.useData((x) => x.url);
const mutableRendererData = pluginApi.renderer.useValtioData();

const orientation = useBreakpointValue({
base: "vertical",
md: "horizontal",
}) as "vertical" | "horizontal";

return (
<Stack dir="column" p={3}>
<Heading>Radio</Heading>

<Button
onClick={() => {
mutableRendererData.isPlaying = !isPlaying;
}}
>
{!isPlaying ? "Play" : "Pause"}
</Button>
<Box>
<Slider
key={orientation}
id="slider"
value={volume ?? 1}
min={0}
max={1}
step={0.01}
orientation={orientation}
colorScheme="teal"
onChange={(v) => {
mutableRendererData.volume = v;
}}
height={{ base: "70vh", md: "100%" }}
width={{ base: "80px", md: "100%" }}
>
<SliderMark value={0.25} mt="1" ml="-2.5" fontSize="sm">
25%
</SliderMark>
<SliderMark value={0.5} mt="1" ml="-2.5" fontSize="sm">
50%
</SliderMark>
<SliderMark value={0.75} mt="1" ml="-2.5" fontSize="sm">
75%
</SliderMark>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<Flex flexDir="column" height="100%">
<Box p={3} bg="gray.900">
<Stack direction="row" alignItems="center" gap={5}>
<Stack direction="row" alignItems="center">
<Text fontWeight="bold" color="white">
<Text>Radio</Text>
</Text>
</Stack>
<Stack direction="row" alignItems="center" spacing={2}>
<Button
size="xs"
bg="transparent"
color="white"
border="1px solid #ffffff6b"
_hover={{ bg: "rgba(255, 255, 255, 0.13)" }}
onClick={() => {
mutableRendererData.isPlaying = !isPlaying;
}}
isDisabled={!playingUrl}
>
{!isPlaying ? <FaPlay /> : <FaPause />}
</Button>
</Stack>
</Stack>
</Box>
</Stack>
<Flex width="100%" height="100%">
<Flex
flexDirection="column"
gap={2}
w="40px"
px="5px"
py={4}
bg="#313131"
alignItems="center"
>
<Text color="white" fontSize="3xs">
Volume
</Text>
<Slider
id="slider"
value={volume ?? 1}
min={0}
max={1}
step={0.01}
orientation="vertical"
onChange={(v) => {
mutableRendererData.volume = v;
}}
my={4}
flex={1}
>
<SliderTrack w={2} bg="black">
<SliderFilledTrack bg="rgb(87, 87, 87)" />
</SliderTrack>
<SliderThumb
rounded="5px"
width="30px"
height="50px"
bg="linear-gradient(#282828 0%, #323232 45%, white 45%, white 55%, #383838 55%, #494949 100%)"
border="1px solid #ffffff1c"
borderTop="1px solid rgba(255, 255, 255, 0.32)"
boxShadow="rgba(0, 0, 0, 0.75) 2px 4px 5px 0px"
/>
</Slider>
</Flex>

<Stack flex={1} dir="column" p={3}>
{radios.map((radio, i) => (
<Stack key={i} direction="row">
<Button
onClick={() => {
if (!isPlaying || playingUrl !== radio.url) {
mutableRendererData.isPlaying = true;
mutableRendererData.url = radio.url;
} else {
mutableRendererData.isPlaying = false;
}
}}
>
{!isPlaying || playingUrl !== radio.url ? (
<FaPlay />
) : (
<FaPause />
)}
</Button>
<Box>
<Text fontSize="md">{radio.title}</Text>
<Link fontSize="xs" href={radio.url} isExternal>
Link
</Link>
</Box>
</Stack>
))}
</Stack>
</Flex>
</Flex>
);
};

Expand Down
54 changes: 54 additions & 0 deletions plugins/radio/view/Renderer/Player.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { perceptualToAmplitude } from "@discordapp/perceptual";
import IcecastMetadataPlayer from "icecast-metadata-player";
import { useEffect, useState } from "react";
import { useDisposable } from "use-disposable";

import { usePluginAPI } from "../pluginApi";

const Player = () => {
const pluginApi = usePluginAPI();

const isPlaying = pluginApi.renderer.useData((x) => x.isPlaying);
const volume = pluginApi.renderer.useData((x) => x.volume);
const url = pluginApi.renderer.useData((x) => x.url!);

const [localIsPlaying, setLocalIsPlaying] = useState(false);

const player = useDisposable(() => {
const player = new IcecastMetadataPlayer(url!, {
onPlay: () => {
setLocalIsPlaying(true);
},
onStop: () => {
setLocalIsPlaying(false);
},
metadataTypes: ["ogg"],
});
return [
player,
() => {
player.detachAudioElement();
},
];
}, []);

useEffect(() => {
if (isPlaying && !localIsPlaying) {
player?.play();
} else if (!isPlaying && localIsPlaying) {
player?.stop();
}
}, [isPlaying, localIsPlaying, player]);

useEffect(() => {
if (player) {
player.audioElement.volume = perceptualToAmplitude(
Math.min(Math.max(0, volume ?? 1), 1),
);
}
}, [player, volume]);

return null;
};

export default Player;
Loading

0 comments on commit 68d0502

Please sign in to comment.