Skip to content

Commit

Permalink
feat(flat-web): setup project (#696)
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx authored Jun 2, 2021
1 parent 6d1c91d commit 0fa4943
Show file tree
Hide file tree
Showing 55 changed files with 5,980 additions and 52 deletions.
5 changes: 2 additions & 3 deletions desktop/renderer-app/src/pages/LoginPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import "./index.less";
import React, { useContext, useEffect, useRef, useState } from "react";
import { constants } from "flat-types";
import { observer } from "mobx-react-lite";
import { withRouter } from "react-router-dom";
import { ipcAsyncByMainWindow, ipcSyncByApp } from "../../utils/ipc";
import { LoginChannelType, LoginPanel } from "flat-components";
import { LoginDisposer } from "./utils";
Expand All @@ -15,7 +14,7 @@ import { runtime } from "../../utils/runtime";
import { useSafePromise } from "../../utils/hooks/lifecycle";
import { WeChatLogin } from "./WeChatLogin";

export const LoginPage = observer<{}>(function LoginPage() {
export const LoginPage = observer(function LoginPage() {
const pushHistory = usePushHistory();
const globalStore = useContext(GlobalStoreContext);
const loginDisposer = useRef<LoginDisposer>();
Expand Down Expand Up @@ -85,4 +84,4 @@ export const LoginPage = observer<{}>(function LoginPage() {
);
});

export default withRouter(LoginPage);
export default LoginPage;
10 changes: 7 additions & 3 deletions web/flat-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@babel/standalone": "7.14.1",
"@hyrious/esbuild-dev": "^0.2.7",
"@netless/eslint-plugin": "^1.1.2",
"@types/loadable__component": "^5.13.3",
"@types/node": "^15.0.2",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3",
Expand All @@ -23,19 +24,22 @@
"eslint-plugin-react-app": "^6.2.2",
"less": "^4.1.1",
"prettier": "^2.3.0",
"vite": "^2.2.4"
"vite": "^2.3.5"
},
"dependencies": {
"@ant-design/icons": "^4.6.2",
"agora-rtc-sdk": "^3.5.2",
"@loadable/component": "^5.15.0",
"agora-rtc-sdk-ng": "^4.5.0",
"antd": "^4.15.4",
"eventemitter3": "^4.0.7",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-use": "^17.2.4",
"white-web-sdk": "^2.12.15"
},
"scripts": {
"postinstall": "esbuild-dev ./scripts/white-web-sdk.ts",
"dev": "vite",
"start": "vite --open",
"build": "vite build",
"serve": "vite preview",
"lint": "lint-staged"
Expand Down
52 changes: 52 additions & 0 deletions web/flat-web/src/AppRoutes/AppRouteContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { ComponentType } from "react";
import { RouteComponentProps } from "react-router-dom";
import loadable from "@loadable/component";
import { ErrorPage } from "flat-components";

export interface AppRouteContainerProps {
Comp: () => Promise<{ default: ComponentType<any> }>;
title: string;
routeProps: RouteComponentProps;
}

export interface AppRouteContainerState {
hasError: boolean;
}

export class AppRouteContainer extends React.PureComponent<
AppRouteContainerProps,
AppRouteContainerState
> {
public constructor(props: AppRouteContainerProps) {
super(props);
this.state = { hasError: false };
}

public static getDerivedStateFromError(): Partial<AppRouteContainerState> {
return { hasError: true };
}

public componentDidMount(): void {
const { title } = this.props;
document.title = title;

// clear selection
window.getSelection()?.removeAllRanges();
}

public componentDidCatch(error: any, errorInfo: any): void {
console.error(error, errorInfo);
}

public render(): React.ReactNode {
if (this.state.hasError) {
return <ErrorPage />;
}

const { Comp, routeProps } = this.props;

return React.createElement(loadable(Comp), routeProps);
}
}

export default AppRouteContainer;
35 changes: 35 additions & 0 deletions web/flat-web/src/AppRoutes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { LastLocationProvider } from "react-router-last-location";
import { RouteConfig, routeConfig } from "../route-config";
import { AppRouteContainer } from "./AppRouteContainer";
import { routePages } from "./route-pages";

export const AppRoutes: React.FC = () => {
return (
<BrowserRouter>
<LastLocationProvider watchOnlyPathname>
<Switch>
{Object.keys(routeConfig).map(((name: keyof RouteConfig) => {
const { path } = routeConfig[name];
const { component, title } = routePages[name];
return (
<Route
key={name}
exact={true}
path={path}
render={routeProps => (
<AppRouteContainer
Comp={component}
title={title}
routeProps={routeProps}
/>
)}
/>
);
}) as (name: string) => React.ReactElement)}
</Switch>
</LastLocationProvider>
</BrowserRouter>
);
};
20 changes: 20 additions & 0 deletions web/flat-web/src/AppRoutes/route-pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ComponentType } from "react";
import { RouteNameType } from "../route-config";

export type RoutePages = {
readonly [key in RouteNameType]: {
readonly title: string;
readonly component: () => Promise<{ default: ComponentType<any> }>;
};
};

export const routePages: RoutePages = {
[RouteNameType.LoginPage]: {
title: "Flat Login",
component: () => import("../pages/LoginPage"),
},
[RouteNameType.HomePage]: {
title: "Flat",
component: () => import("../pages/Homepage"),
},
};
184 changes: 184 additions & 0 deletions web/flat-web/src/apiMiddleware/CloudRecording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { updateRecordEndTime } from "./flatServer";
import {
cloudRecordAcquire,
CloudRecordStopResult,
cloudRecordStop,
CloudRecordAcquirePayload,
CloudRecordStartPayload,
cloudRecordStart,
CloudRecordQueryResult,
cloudRecordQuery,
cloudRecordUpdateLayout,
CloudRecordUpdateLayoutPayload,
CloudRecordUpdateLayoutResult,
} from "./flatServer/agora";

/**
* 声网云录制
*/
export class CloudRecording {
/**
* 录制模式,支持单流模式 individual 和合流模式 mix (默认模式)。
* - 单流模式下,分开录制频道内每个 UID 的音频流和视频流,每个 UID 均有其对应的音频文件和视频文件。
* - 合流模式下,频道内所有 UID 的音视频混合录制为一个音视频文件。
*/
public mode: "individual" | "mix" = "mix";

/** 待录制的频道名 */
public roomUUID: string;

/** 通过 acquire 请求获取的 resource ID。 */
public get resourceid(): string {
if (this._resourceid === null || this._resourceid === undefined) {
throw new Error("No resourceid. Use `couldRecording.acquire` to acquire one.");
}
return this._resourceid;
}

/** 通过 start 请求获取的录制 ID。 */
public get sid(): string {
if (this._sid === null || this._sid === undefined) {
throw new Error("No sid. Use `couldRecording.start` to acquire one.");
}
return this._sid;
}

public get isRecording(): boolean {
return this._isRecording;
}

private _resourceid: string | null = null;
private _sid: string | null = null;
private _isRecording = false;

private _reportEndTimeTimeout = NaN;

public constructor(init: {
/** 待录制的频道名 */
roomUUID: string;
}) {
this.roomUUID = init.roomUUID;
}

/**
* 开始录制
*
* 获取录制资源 ID, 一个 resource ID 仅可用于一次录制,五分钟内需调用 start 开始录制。
* 获取 resource ID 后,调用该方法开始云端录制。
*
*/
public async start(
startPayload: CloudRecordStartPayload["agoraData"]["clientRequest"] = {
recordingConfig: {
subscribeUidGroup: 0,
transcodingConfig: {
width: 640,
height: 360,
fps: 15,
bitrate: 500,
defaultUserBackgroundImage: process.env.CLOUD_RECORDING_DEFAULT_AVATAR,
},
},
},
acquirePayload: CloudRecordAcquirePayload["agoraData"]["clientRequest"] = {
resourceExpiredHour: 24,
},
): Promise<void> {
if (this._isRecording) {
return;
}

this._isRecording = true;
try {
const { resourceId } = await cloudRecordAcquire({
roomUUID: this.roomUUID,
agoraData: { clientRequest: acquirePayload },
});
this._resourceid = resourceId;

const { sid } = await cloudRecordStart({
roomUUID: this.roomUUID,
agoraParams: {
resourceid: this.resourceid,
mode: this.mode,
},
agoraData: { clientRequest: startPayload },
});
this._sid = sid;
this.startReportEndTime();
} catch (e) {
this._isRecording = false;
throw e;
}
}

/** 停止录制 */
public async stop(): Promise<CloudRecordStopResult> {
window.clearTimeout(this._reportEndTimeTimeout);
try {
const response = await cloudRecordStop({
roomUUID: this.roomUUID,
agoraParams: {
resourceid: this.resourceid,
mode: this.mode,
sid: this.sid,
},
});
this._isRecording = false;
return response;
} catch (e) {
this._isRecording = false;
throw e;
}
}

/**
* 开始录制后,你可以调用 query 查询录制状态。
* query 请求仅在会话内有效。如果录制启动错误,或录制已结束,调用 query 将返回 404。
*/
public async query(): Promise<CloudRecordQueryResult> {
try {
return cloudRecordQuery({
roomUUID: this.roomUUID,
agoraParams: {
resourceid: this.resourceid,
mode: this.mode,
sid: this.sid,
},
});
} catch (e) {
if (e.status === 404) {
this._isRecording = false;
}
throw e;
}
}

/** 在录制过程中,可以随时调用该方法更新合流布局的设置。 */
public async updateLayout(
payload: CloudRecordUpdateLayoutPayload["agoraData"]["clientRequest"],
): Promise<CloudRecordUpdateLayoutResult> {
return cloudRecordUpdateLayout({
roomUUID: this.roomUUID,
agoraParams: {
resourceid: this.resourceid,
mode: this.mode,
sid: this.sid,
},
agoraData: { clientRequest: payload },
});
}

/** Report endTime to flat server */
private startReportEndTime(): void {
this._reportEndTimeTimeout = window.setInterval(() => {
if (this._isRecording) {
void updateRecordEndTime(this.roomUUID);
} else {
window.clearInterval(this._reportEndTimeTimeout);
}
}, 10000);
}
}

export default CloudRecording;
Loading

0 comments on commit 0fa4943

Please sign in to comment.