feat: init agora

This commit is contained in:
Tristan Yang
2023-03-23 10:33:23 +08:00
parent 07ab652221
commit 88a8f061b5
12 changed files with 184 additions and 17 deletions
+12 -6
View File
@@ -9,17 +9,18 @@ import {
KEY_UID
} from "../config";
import { AuthData, RenewTokenResponse } from "../../types/auth";
import { User } from "../../types/user";
import { StoredUser } from "./users";
// import { updateUsersByLogs } from './users';
interface State {
initialized: boolean;
guest: boolean;
user: User | undefined;
user: StoredUser | undefined;
token: string;
expireTime: number;
refreshToken: string;
roleChanged: boolean;
voice: boolean;
}
const loginUser = localStorage.getItem(KEY_LOGIN_USER) || "";
const initialState: State = {
@@ -29,7 +30,8 @@ const initialState: State = {
token: localStorage.getItem(KEY_TOKEN) || "",
expireTime: Number(localStorage.getItem(KEY_EXPIRE) || +new Date()),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN) || "",
roleChanged: false
roleChanged: false,
voice: false
};
const emptyState: State = {
@@ -39,7 +41,8 @@ const emptyState: State = {
token: "",
expireTime: +new Date(),
refreshToken: "",
roleChanged: false
roleChanged: false,
voice: false
};
const authDataSlice = createSlice({
@@ -64,7 +67,7 @@ const authDataSlice = createSlice({
localStorage.setItem(KEY_REFRESH_TOKEN, refresh_token);
localStorage.setItem(KEY_UID, `${uid}`);
},
updateLoginUser(state, { payload }: PayloadAction<Partial<User>>) {
updateLoginUser(state, { payload }: PayloadAction<Partial<StoredUser>>) {
if (!state.user) return;
const obj = { ...state.user, ...payload };
@@ -82,6 +85,9 @@ const authDataSlice = createSlice({
updateRoleChanged(state, action: PayloadAction<boolean>) {
state.roleChanged = action.payload;
},
updateVoiceStatus(state, action: PayloadAction<boolean>) {
state.voice = action.payload;
},
resetAuthData() {
// remove local data
localStorage.removeItem(KEY_EXPIRE);
@@ -128,5 +134,5 @@ const authDataSlice = createSlice({
// }
});
export const { updateInitialized, updateLoginUser, setAuthData, resetAuthData, updateToken, updateRoleChanged } = authDataSlice.actions;
export const { updateInitialized, updateLoginUser, setAuthData, resetAuthData, updateToken, updateRoleChanged, updateVoiceStatus } = authDataSlice.actions;
export default authDataSlice.reducer;
+1
View File
@@ -6,6 +6,7 @@ import { UserLog, UserState } from "../../types/sse";
export interface StoredUser extends User {
online?: boolean;
voice?: boolean;
avatar?: string;
}
+2 -2
View File
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C17.5228 2 22 6.47715 22 12V19C22 20.6569 20.6569 22 19 22H16C15.4477 22 15 21.5523 15 21V15C15 14.4477 15.4477 14 16 14H20.5V12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12V14H8C8.55228 14 9 14.4477 9 15V21C9 21.5523 8.55228 22 8 22H5C3.34315 22 2 20.6569 2 19V12C2 6.47715 6.47715 2 12 2Z" fill="#70707B"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="#70707B" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C17.5228 2 22 6.47715 22 12V19C22 20.6569 20.6569 22 19 22H16C15.4477 22 15 21.5523 15 21V15C15 14.4477 15.4477 14 16 14H20.5V12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12V14H8C8.55228 14 9 14.4477 9 15V21C9 21.5523 8.55228 22 8 22H5C3.34315 22 2 20.6569 2 19V12C2 6.47715 6.47715 2 12 2Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 444 B

After

Width:  |  Height:  |  Size: 432 B

+2
View File
@@ -16,6 +16,7 @@ import IconPin from "../../../assets/icons/pin.svg";
import { useAppSelector } from "../../../app/store";
import GoBackNav from "../../../common/component/GoBackNav";
import Members from "./Members";
import VoiceChat from "../VoiceChat";
type Props = {
cid?: number;
dropFiles?: File[];
@@ -83,6 +84,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
</li>
</Tippy>
</Tooltip>
<VoiceChat channel={`channel_${cid}`} />
<Tooltip tip={t("fav")} placement="left">
<Tippy
placement="left-start"
+37
View File
@@ -0,0 +1,37 @@
import React from 'react';
import { useAppSelector } from '../../app/store';
import User from '../../common/component/User';
import IconHeadphone from '../../assets/icons/headphone.svg';
import IconMic from '../../assets/icons/mic.on.svg';
type Props = {}
const RTCWidget = (props: Props) => {
const { loginUser, voicing } = useAppSelector(store => { return { loginUser: store.authData.user, voicing: store.authData.voice }; });
if (!loginUser) return null;
return (
<div className='bg-gray-100 dark:bg-gray-900 flex flex-col p-2 rounded-full m-3 text-sm'>
{voicing && <div className="flex justify-between items-center">
<div className="flex items-center gap-3 text-green-800">
Voice Connected
</div>
</div>}
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<User uid={loginUser.uid} compact />
<div className="flex flex-col">
<span className='dark:text-white text-sm'>{loginUser.name}</span>
<span className='text-gray-400 text-xs'>#{loginUser.uid}</span>
</div>
</div>
<div className="flex gap-2">
<IconHeadphone role="button" />
<IconMic role="button" />
</div>
</div>
</div>
);
};
export default RTCWidget;
+1 -1
View File
@@ -74,7 +74,7 @@ const SessionList: FC<Props> = ({ tempSession }) => {
return (
<>
<ul ref={ref} className="flex flex-col gap-0.5 p-2 overflow-auto">
<ul ref={ref} className="flex flex-1 flex-col gap-0.5 p-2 overflow-auto">
<ViewportList
initialPrerender={10}
viewportRef={ref}
+70
View File
@@ -0,0 +1,70 @@
import { useEffect } from 'react';
import AgoraRTC from "agora-rtc-sdk-ng";
import useConfig from '../../../common/hook/useConfig';
import { useDispatch } from 'react-redux';
import { updateVoiceStatus } from '../../../app/slices/auth.data';
type Props = {
channel: string,
uid: number,
voicing?: boolean;
}
const Dashboard = ({ voicing, uid, channel }: Props) => {
const dispatch = useDispatch();
console.log("aaaaa");
const { agoraConfig } = useConfig("agora");
useEffect(() => {
console.log("aaa", agoraConfig);
const startConnect = async () => {
// Create an instance of the Agora Engine
const agoraEngine = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
// Listen for the "user-published" event to retrieve an AgoraRTCRemoteUser object.
agoraEngine.on("user-published", async (user, mediaType) => {
// Subscribe to the remote user when the SDK triggers the "user-published" event.
await agoraEngine.subscribe(user, mediaType);
console.log("subscribe success");
// // Subscribe and play the remote audio track.
// if (mediaType == "audio")
// {
// channelParameters.remoteUid=user.uid;
// // Get the RemoteAudioTrack object from the AgoraRTCRemoteUser object.
// channelParameters.remoteAudioTrack = user.audioTrack;
// // Play the remote audio track.
// channelParameters.remoteAudioTrack.play();
// console.log("Remote user connected: " + user.uid);
// }
// Listen for the "user-unpublished" event.
agoraEngine.on("user-unpublished", user => {
console.log(user.uid + "has left the channel");
console.log("Remote user has left the channel");
});
});
// Join a channel.
console.log("join data ", agoraConfig);
await agoraEngine.join(agoraConfig!.app_id, agoraConfig!.rtm_key, agoraConfig!.rtm_secret, uid);
console.log("Joined channel: " + agoraConfig!.rtm_key);
// // Create a local audio track from the microphone audio.
// channelParameters.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
// // Publish the local audio track in the channel.
// await agoraEngine.publish(channelParameters.localAudioTrack);
// console.log("Publish success!");
dispatch(updateVoiceStatus(true));
};
if (!voicing && agoraConfig) {
startConnect();
}
}, [voicing, agoraConfig, uid, channel]);
return (
<div>Dashboard</div>
);
};
export default Dashboard;
+36
View File
@@ -0,0 +1,36 @@
// import React from 'react';
import Tippy from '@tippyjs/react';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '../../../app/store';
import IconHeadphone from '../../../assets/icons/headphone.svg';
import Tooltip from '../../../common/component/Tooltip';
import Dashboard from './Dashboard';
type Props = {
channel: string
}
const VoiceChat = ({ channel }: Props) => {
const { loginUser, voice } = useAppSelector(store => { return { loginUser: store.authData.user, voice: store.authData.voice }; });
const { t } = useTranslation("chat");
const toolClass = `relative cursor-pointer`;
if (!loginUser) return null;
return (
<Tooltip tip={t("fav")} placement="left">
<Tippy
placement="left-start"
popperOptions={{ strategy: "fixed" }}
offset={[0, 164]}
interactive
trigger="click"
content={<Dashboard uid={loginUser.uid} channel={channel} voicing={voice} />}
>
<li className={`${toolClass}`}>
<IconHeadphone className="fill-gray-500" />
</li>
</Tippy>
</Tooltip>
);
};
export default VoiceChat;
+4 -2
View File
@@ -14,6 +14,7 @@ import GuestBlankPlaceholder from "./GuestBlankPlaceholder";
import GuestChannelChat from "./GuestChannelChat";
import GuestSessionList from "./GuestSessionList";
import ErrorCatcher from "../../common/component/ErrorCatcher";
import RTCWidget from "./RTCWidget";
function ChatPage() {
const isHomePath = useMatch(`/`);
@@ -62,9 +63,10 @@ function ChatPage() {
)}>
<Server readonly={isGuest} />
{isGuest ? <GuestSessionList /> : <SessionList tempSession={tmpSession} />}
{isGuest && <footer className="hidden md:block py-1 text-xs text-gray-300 dark:text-gray-700 text-center">
{isGuest ? <footer className="hidden md:block py-1 text-xs text-gray-300 dark:text-gray-700 text-center">
Host your own <a href="https://voce.chat" target="_blank" rel="noopener noreferrer" className="text-gray-400 dark:text-gray-600">voce.chat</a>
</footer>}
</footer> : <RTCWidget />}
</div>
<div className={clsx(`right-container md:rounded-r-2xl w-full bg-white dark:!bg-gray-700`, placeholderVisible && "h-full flex-center", isMainPath && "hidden md:flex")}>
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
+5 -5
View File
@@ -11,7 +11,7 @@ import BotConfig from "./BotConfig";
import APIDocument from "./APIDocument";
import ManageMembers from "../../common/component/ManageMembers";
import Version from "../../common/component/Version";
// import ConfigAgora from "./config/Agora";
import ConfigAgora from "./config/Agora";
import { useAppSelector } from "../../app/store";
import ServerVersionChecker from "../../common/component/ServerVersionChecker";
import useLicense from "../../common/hook/useLicense";
@@ -47,10 +47,10 @@ const navs = [
name: "firebase",
component: <ConfigFirebase />
},
// {
// name: "agora",
// component: <ConfigAgora />
// },
{
name: "agora",
component: <ConfigAgora />
},
{
name: "smtp",
component: <ConfigSMTP />