feat: 声网插件增加ahooks并优化初始化逻辑

pull/64/head
moonrailgun 2 years ago
parent 0ee28554c2
commit 928f1a25b2

@ -947,6 +947,7 @@ importers:
server/plugins/com.msgbyte.agora:
specifiers:
'@rollup/plugin-replace': ^5.0.2
'@types/react': 18.0.20
got: 11.8.6
mini-star: '*'
@ -957,6 +958,7 @@ importers:
got: 11.8.6
tailchat-server-sdk: link:../../packages/sdk
devDependencies:
'@rollup/plugin-replace': 5.0.2
'@types/react': 18.0.20
mini-star: 2.0.5
normalize-path: 3.0.0
@ -966,10 +968,12 @@ importers:
specifiers:
'@types/styled-components': ^5.1.26
agora-rtc-react: ^1.1.3
ahooks: ^3.7.4
react: 18.2.0
styled-components: ^5.3.6
dependencies:
agora-rtc-react: 1.1.3_react@18.2.0
ahooks: 3.7.4_react@18.2.0
devDependencies:
'@types/styled-components': 5.1.26
react: 18.2.0
@ -8394,6 +8398,19 @@ packages:
rollup: 2.79.1
dev: true
/@rollup/plugin-replace/5.0.2:
resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.2
magic-string: 0.27.0
dev: true
/@rollup/plugin-url/6.1.0_rollup@2.78.1:
resolution: {integrity: sha512-FJNWBnBB7nLzbcaGmu1no+U/LlRR67TtgfRFP+VEKSrWlDTE6n9jMns/N4Q/VL6l4x6kTHQX4HQfwTcldaAfHQ==}
engines: {node: '>=10.0.0'}
@ -8446,6 +8463,20 @@ packages:
estree-walker: 2.0.2
picomatch: 2.3.1
/@rollup/pluginutils/5.0.2:
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.0
estree-walker: 2.0.2
picomatch: 2.3.1
dev: true
/@seald-io/binary-search-tree/1.0.2:
resolution: {integrity: sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==}
dev: false
@ -12475,6 +12506,24 @@ packages:
screenfull: 5.2.0
dev: false
/ahooks/3.7.4_react@18.2.0:
resolution: {integrity: sha512-hvgdqzPUKXn95mK3cGlDCi/ZZqv+FRibCUCFT8zW3hCwLGvixVfnHrIW2/2lgzPdLo8mLjp/XOdIJvcPvE2lgQ==}
engines: {node: '>=8.0.0'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@types/js-cookie': 2.2.7
ahooks-v3-count: 1.0.0
dayjs: 1.11.7
intersection-observer: 0.12.2
js-cookie: 2.2.1
lodash: 4.17.21
react: 18.2.0
resize-observer-polyfill: 1.5.1
screenfull: 5.2.0
tslib: 2.4.1
dev: false
/airbnb-js-shims/2.2.1:
resolution: {integrity: sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ==}
dependencies:
@ -23683,6 +23732,13 @@ packages:
sourcemap-codec: 1.4.8
dev: false
/magic-string/0.27.0:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/make-dir/1.3.0:
resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==}
engines: {node: '>=4'}

@ -1,6 +1,7 @@
const path = require('path');
const copy = require('rollup-plugin-copy');
const normalize = require('normalize-path');
const replace = require('@rollup/plugin-replace');
const pluginRoot = path.resolve(__dirname, './web');
const outDir = path.resolve(__dirname, '../../public');
@ -49,5 +50,8 @@ module.exports = {
dest: normalize(item.dest, false),
})),
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
};

@ -11,6 +11,7 @@
"build:web:watch": "ministar watchPlugin all"
},
"devDependencies": {
"@rollup/plugin-replace": "^5.0.2",
"@types/react": "18.0.20",
"mini-star": "*",
"normalize-path": "^3.0.0",

@ -8,7 +8,8 @@
"sync:declaration": "tailchat declaration github"
},
"dependencies": {
"agora-rtc-react": "^1.1.3"
"agora-rtc-react": "^1.1.3",
"ahooks": "^3.7.4"
},
"devDependencies": {
"@types/styled-components": "^5.1.26",

@ -1,25 +1,34 @@
import { IconBtn } from '@capital/component';
import type { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-react';
import React, { useState } from 'react';
import { useClient } from './client';
import { useClient, useMicrophoneAndCameraTracks } from './client';
export const Controls: React.FC<{
tracks: [IMicrophoneAudioTrack, ICameraVideoTrack];
setStart: React.Dispatch<React.SetStateAction<boolean>>;
onClose: () => void;
}> = React.memo((props) => {
const client = useClient();
const { tracks, setStart } = props;
const [trackState, setTrackState] = useState({ video: true, audio: true });
const [trackState, setTrackState] = useState({ video: false, audio: false });
const { ready, tracks } = useMicrophoneAndCameraTracks();
const mute = async (type: 'audio' | 'video') => {
if (type === 'audio') {
await tracks[0].setEnabled(!trackState.audio);
if (trackState.audio === true) {
// await tracks[0].setEnabled(false);
await client.unpublish(tracks[0]);
} else {
// await tracks[0].setEnabled(true);
await client.publish(tracks[0]);
}
setTrackState((ps) => {
return { ...ps, audio: !ps.audio };
});
} else if (type === 'video') {
await tracks[1].setEnabled(!trackState.video);
if (trackState.video === true) {
// await tracks[1].setEnabled(false);
await client.unpublish(tracks[1]);
} else {
// await tracks[1].setEnabled(true);
await client.publish(tracks[1]);
}
setTrackState((ps) => {
return { ...ps, video: !ps.video };
});
@ -32,7 +41,6 @@ export const Controls: React.FC<{
// we close the tracks to perform cleanup
tracks[0].close();
tracks[1].close();
setStart(false);
props.onClose();
};
@ -41,6 +49,7 @@ export const Controls: React.FC<{
<IconBtn
icon={trackState.video ? 'mdi:video' : 'mdi:video-off'}
title={trackState.video ? '关闭摄像头' : '开启摄像头'}
disabled={!ready}
size="large"
onClick={() => mute('video')}
/>
@ -48,6 +57,7 @@ export const Controls: React.FC<{
<IconBtn
icon={trackState.audio ? 'mdi:microphone' : 'mdi:microphone-off'}
title={trackState.audio ? '关闭麦克风' : '开启麦克风'}
disabled={!ready}
size="large"
onClick={() => mute('audio')}
/>

@ -1,16 +1,10 @@
import {
AgoraVideoPlayer,
IAgoraRTCRemoteUser,
ICameraVideoTrack,
IMicrophoneAudioTrack,
} from 'agora-rtc-react';
import { AgoraVideoPlayer, IAgoraRTCRemoteUser } from 'agora-rtc-react';
import React from 'react';
export const Videos: React.FC<{
users: IAgoraRTCRemoteUser[];
tracks: [IMicrophoneAudioTrack, ICameraVideoTrack];
}> = React.memo((props) => {
const { users, tracks } = props;
const { users } = props;
return (
<div>

@ -1,15 +1,12 @@
import React, { useEffect, useState } from 'react';
import { getJWTUserInfo } from '@capital/common';
import React, { useEffect, useRef, useState } from 'react';
import { getJWTUserInfo, isValidStr, showErrorToasts } from '@capital/common';
import type { IAgoraRTCRemoteUser } from 'agora-rtc-react';
import styled from 'styled-components';
import {
appId,
token,
useClient,
useMicrophoneAndCameraTracks,
} from './client';
import { appId, token, useClient } from './client';
import { Videos } from './Videos';
import { Controls } from './Controls';
import { LoadingSpinner } from '@capital/component';
import { useMemoizedFn } from 'ahooks';
const FloatWindow = styled.div`
z-index: 100;
@ -81,59 +78,63 @@ export const FloatMeetingWindow: React.FC<{
}> = React.memo((props) => {
const [folder, setFolder] = useState(false);
const client = useClient();
const { ready, tracks } = useMicrophoneAndCameraTracks();
const channelName = props.meetingId;
const [users, setUsers] = useState<IAgoraRTCRemoteUser[]>([]);
const [start, setStart] = useState<boolean>(false);
const initedRef = useRef(false);
useEffect(() => {
// function to initialise the SDK
const init = async (channel: string) => {
client.on('user-published', async (user, mediaType) => {
await client.subscribe(user, mediaType);
console.log('subscribe success');
if (mediaType === 'video') {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === 'audio') {
user.audioTrack?.play();
}
});
client.on('user-unpublished', (user, type) => {
console.log('unpublished', user, type);
if (type === 'audio') {
user.audioTrack?.stop();
}
if (type === 'video') {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
const init = useMemoizedFn(async (channelName: string) => {
client.on('user-published', async (user, mediaType) => {
await client.subscribe(user, mediaType);
console.log('subscribe success');
if (mediaType === 'video') {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === 'audio') {
user.audioTrack?.play();
}
});
client.on('user-left', (user) => {
console.log('leaving', user);
client.on('user-unpublished', (user, type) => {
console.log('unpublished', user, type);
if (type === 'audio') {
user.audioTrack?.stop();
}
if (type === 'video') {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
client.on('user-left', (user) => {
console.log('leaving', user);
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
});
const { _id } = await getJWTUserInfo();
await client.join(appId, channel, token, _id);
if (tracks) {
await client.publish([tracks[0], tracks[1]]);
}
const { _id } = await getJWTUserInfo();
try {
await client.join(appId, channelName, token, _id);
setStart(true);
};
} catch (err) {
showErrorToasts(err);
}
});
if (ready && tracks) {
console.log('init ready');
useEffect(() => {
if (initedRef.current) {
return;
}
if (isValidStr(channelName)) {
init(channelName);
initedRef.current = true;
}
}, [channelName, client, ready, tracks]);
}, [channelName]);
return (
<FloatWindow
@ -142,17 +143,15 @@ export const FloatMeetingWindow: React.FC<{
}}
>
<div className="body">
{start && tracks && <Videos users={users} tracks={tracks} />}
{start ? (
<Videos users={users} />
) : (
<LoadingSpinner tip={'正在加入通话...'} />
)}
</div>
<div className="controller">
{ready && tracks && (
<Controls
tracks={tracks}
setStart={setStart}
onClose={props.onClose}
/>
)}
<Controls onClose={props.onClose} />
</div>
<div className="folder-btn" onClick={() => setFolder(!folder)}>

Loading…
Cancel
Save