Skip to content

Commit

Permalink
Merge pull request #67 from brendantwh/voice
Browse files Browse the repository at this point in the history
Add Agora.io for voice chat
  • Loading branch information
brendantwh authored Nov 10, 2024
2 parents 37cfbfa + ecfee41 commit b84e9aa
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 18 deletions.
39 changes: 39 additions & 0 deletions frontend/app/session/AgoraRTCProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// credits to https://github.com/AgoraIO-Extensions/agora-rtc-react/issues/181#issuecomment-1886586029

"use client";

import { useState, useEffect, ReactNode, useRef } from "react";
import type { ClientConfig, IAgoraRTCClient } from "agora-rtc-react";
import dynamic from "next/dynamic";

const AgoraRTCProviderPrimitive = dynamic(
() =>
import("agora-rtc-react").then(({ AgoraRTCProvider }) => AgoraRTCProvider),
{
ssr: false,
},
);

export default function AgoraRTCProvider(props: {
clientConfig: ClientConfig;
children: ReactNode;
}) {
const clientConfigRef = useRef<ClientConfig>(props.clientConfig);
const [client, setClient] = useState<IAgoraRTCClient>();

useEffect(() => {
const initSdk = async () => {
const AgoraRTC = (await import("agora-rtc-react")).default;
setClient(AgoraRTC.createClient(clientConfigRef.current));
};
initSdk();
}, []);

return (
client && (
<AgoraRTCProviderPrimitive client={client}>
{props.children}
</AgoraRTCProviderPrimitive>
)
);
}
54 changes: 37 additions & 17 deletions frontend/app/session/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { getCookie } from '@/app/utils/cookie-manager';
import { HocuspocusProvider, HocuspocusProviderWebsocket } from '@hocuspocus/provider';
import * as Y from 'yjs';
import Markdown from 'react-markdown'
import { LocalUser, RemoteUser, useJoin, useLocalMicrophoneTrack, usePublish, useRemoteUsers } from "agora-rtc-react";

const DynamicCodeEditor = dynamic(() => import('../code-editor/code-editor'), { ssr: false });
const DynamicTextEditor = dynamic(
Expand Down Expand Up @@ -50,6 +51,8 @@ export default function Session() {
const [isEndDialogOpen, setIsEndDialogOpen] = useState(false);
const [language, setLanguage] = useState("javascript");

const [calling, setCalling] = useState(false); // Is calling

const codeDocRef = useRef<Y.Doc>();
const codeProviderRef = useRef<HocuspocusProvider | null>(null);
const notesProviderRef = useRef<HocuspocusProvider | null>(null);
Expand Down Expand Up @@ -206,30 +209,36 @@ export default function Session() {
if (isSessionEnded) {
codeProvider.sendStateless("endSession");
}

setCalling(true);
}, [isSessionEnded, params.id, questionId, router]);

useJoin({appid: "9da9d118c6a646d1a010b4b227ca1345", channel: `voice-${params.id}`, token: null}, calling);

const { localMicrophoneTrack } = useLocalMicrophoneTrack(isMicEnabled);
usePublish([localMicrophoneTrack]);

const remoteUsers = useRemoteUsers();

const handleMicToggle = useCallback(() => {
const newMicState = !isMicEnabled;
setIsMicEnabled(newMicState);

localMicrophoneTrack?.setMuted(!newMicState);

toast(newMicState ? 'Your mic is now unmuted' : 'Your mic is now muted', {
className: "justify-center font-sans text-sm",
duration: 1500,
icon: newMicState ?
<MicIcon className="h-4 w-4 mr-2 text-green-500" /> :
<MicOffIcon className="h-4 w-4 mr-2 text-red-500" />
});
}, [isMicEnabled, localMicrophoneTrack]);

if (!isClient) {
return SessionLoading();
}

const handleMicToggle = () => {
setIsMicEnabled(!isMicEnabled);
if (!isMicEnabled) {
toast('Your mic is now unmuted', {
className: "justify-center font-sans text-sm",
duration: 1500,
icon: <MicIcon className="h-4 w-4 mr-2 text-green-500" />,
});
} else {
toast('Your mic is now muted', {
className: "justify-center font-sans text-sm",
duration: 1500,
icon: <MicOffIcon className="h-4 w-4 mr-2 text-red-500" />,
});
}
};

function handleCancel() {
if (controller) {
controller.abort(); // Cancel the API call
Expand All @@ -251,15 +260,26 @@ export default function Session() {
<span className="font-semibold">{peerUsername}</span>
</div>
<div className="mr-[52px]">
<LocalUser
micOn={isMicEnabled}
className="hidden"
></LocalUser>
<Toggle
onPressedChange={handleMicToggle}
pressed={isMicEnabled}
>
{isMicEnabled ? (
<MicIcon className="size-5 text-green-500" />
) : (
<MicOffIcon className="size-5 text-red-500" />
)}
</Toggle>
{remoteUsers.map((user) => (
<div className="user hidden" key={user.uid}>
<RemoteUser user={user}>
</RemoteUser>
</div>
))}
</div>
<div className="">
<Dialog open={isEndDialogOpen} onOpenChange={setIsEndDialogOpen}>
Expand Down
8 changes: 7 additions & 1 deletion frontend/app/session/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { deleteAllCookies, getCookie } from "../utils/cookie-manager";
import { isTokenExpired } from "../utils/token-utils";
import { ClientConfig } from "agora-rtc-react";
import AgoraRTCProvider from "./AgoraRTCProvider";

export default function SessionLayout({
children,
Expand All @@ -27,9 +29,13 @@ export default function SessionLayout({
authenticateUser();
}, [pathname, router]);

const client:ClientConfig = { mode: "rtc", codec: "vp8" };

return (
<div className="min-h-screen bg-white">
{children}
<AgoraRTCProvider clientConfig={client}>
{children}
</AgoraRTCProvider>
</div>
);
}
Loading

0 comments on commit b84e9aa

Please sign in to comment.