feat: 声网插件 正在发言指示器

pull/64/head
moonrailgun 2 years ago
parent 141db8f1cf
commit 076c907a05

@ -53,6 +53,7 @@ export {
useGroupPanelInfo,
sendMessage,
showMessageTime,
joinArray,
} from 'tailchat-shared';
export { useLocation, useNavigate } from 'react-router';

@ -164,6 +164,8 @@ declare module '@capital/common' {
export const showMessageTime: any;
export const joinArray: any;
export const useLocation: any;
export const useNavigate: any;
@ -244,21 +246,6 @@ declare module '@capital/common' {
export const pluginPanelActions: any;
interface BasePluginPanelActionProps {
/**
*
*/
name: string;
/**
*
*/
label: string;
/**
* iconify
*/
icon: string;
}
export const regPluginPanelAction: (
action:
| {
@ -311,6 +298,10 @@ declare module '@capital/common' {
export const regPluginGroupTextPanelExtraMenu: any;
export const pluginUserExtraInfo: any;
export const regUserExtraInfo: any;
export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => {
@ -488,4 +479,8 @@ declare module '@capital/component' {
}>;
export const Markdown: any;
export const Webview: any;
export const WebviewKeepAlive: any;
}

@ -65,6 +65,15 @@ export const MeetingView: React.FC<MeetingViewProps> = React.memo((props) => {
useMeetingStore.getState().removeUser(user);
});
client.on('volume-indicator', (volumes) => {
useMeetingStore.setState({
volumes: volumes.map((v) => ({
uid: String(v.uid),
level: v.level,
})),
});
});
try {
const { _id } = await getJWTUserInfo();
const { data } = await request.post('generateJoinInfo', {
@ -75,6 +84,7 @@ export const MeetingView: React.FC<MeetingViewProps> = React.memo((props) => {
await client.join(appId, channelName, token, _id);
await client.enableDualStream();
client.enableAudioVolumeIndicator();
setStart(true);
} catch (err) {
showErrorToasts(err);

@ -0,0 +1,23 @@
import React from 'react';
import { joinArray } from '@capital/common';
import { useMeetingStore } from './store';
import { UserName } from '@capital/component';
import { Translate } from '../translate';
export const SpeakerNames: React.FC = React.memo(() => {
const volumes = useMeetingStore((state) => state.volumes);
const activeUserNames = volumes
.filter((v) => v.level >= 60)
.map((v) => <UserName key={v.uid} userId={v.uid} />);
return (
<span>
<span>{joinArray(activeUserNames, ',')}</span>
{activeUserNames.length > 0
? ' ' + Translate.isSpeaking
: Translate.nomanSpeaking}
</span>
);
});
SpeakerNames.displayName = 'SpeakerNames';

@ -5,7 +5,9 @@ import styled from 'styled-components';
import { useClient, useMicrophoneAndCameraTracks } from './client';
import { useMeetingStore } from './store';
const Root = styled.div`
const Root = styled.div<{
active?: boolean;
}>`
width: 95%;
height: auto;
position: relative;
@ -19,6 +21,10 @@ const Root = styled.div`
justify-content: center;
align-items: center;
border-width: 3px;
border-color: ${(props) => (props.active ? '#7ab157;' : 'transparent')};
transition: border-color 0.2s;
.player {
width: 100%;
height: 100%;
@ -36,9 +42,10 @@ export const VideoView: React.FC<{
user: IAgoraRTCRemoteUser;
}> = (props) => {
const user = props.user;
const active = useVolumeActive(String(user.uid));
return (
<Root>
<Root active={active}>
{user.hasVideo ? (
<AgoraVideoPlayer className="player" videoTrack={user.videoTrack} />
) : (
@ -55,9 +62,10 @@ export const OwnVideoView: React.FC<{}> = React.memo(() => {
const { ready, tracks } = useMicrophoneAndCameraTracks();
const client = useClient();
const mediaPerm = useMeetingStore((state) => state.mediaPerm);
const active = useVolumeActive(String(client.uid));
return (
<Root>
<Root active={active}>
{ready && mediaPerm.video ? (
<AgoraVideoPlayer className="player" videoTrack={tracks[1]} />
) : (
@ -69,3 +77,12 @@ export const OwnVideoView: React.FC<{}> = React.memo(() => {
);
});
OwnVideoView.displayName = 'OwnVideoView';
function useVolumeActive(uid: string) {
const volume = useMeetingStore((state) =>
state.volumes.find((v) => v.uid === uid)
);
const volumeLevel = volume?.level ?? 0;
return volumeLevel >= 60;
}

@ -15,6 +15,16 @@ interface MeetingState {
*
*/
mediaPerm: MediaPerm;
/**
*
*/
volumes: {
/**
* , 0~100, 60.
*/
level: number;
uid: string;
}[];
appendUser: (user: IAgoraRTCRemoteUser) => void;
removeUser: (user: IAgoraRTCRemoteUser) => void;
/**
@ -29,6 +39,7 @@ interface MeetingState {
export const useMeetingStore = create<MeetingState>((set) => ({
users: [],
mediaPerm: { video: false, audio: false },
volumes: [],
appendUser: (user: IAgoraRTCRemoteUser) => {
set((state) => ({
users: [...state.users, user],

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import styled from 'styled-components';
import { ErrorBoundary } from '@capital/component';
import { MeetingView, MeetingViewProps } from './MeetingView';
import { SpeakerNames } from './SpeakerNames';
const FloatWindow = styled.div`
z-index: 100;
@ -17,18 +18,23 @@ const FloatWindow = styled.div`
flex-direction: column;
.folder-btn {
background-color: var(--tc-content-background-color);
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
position: absolute;
bottom: -30px;
height: 30px;
line-height: 30px;
left: 50%;
width: 60px;
margin-left: -30px;
text-align: center;
cursor: pointer;
border-radius: 0 0 3px 3px;
left: 0;
right: 0;
display: flex;
> div {
background-color: var(--tc-content-background-color);
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
height: 30px;
line-height: 30px;
cursor: pointer;
border-radius: 0 0 3px 3px;
margin: auto;
display: inline-block;
padding: 0 8px;
}
}
`;
@ -50,7 +56,10 @@ export const FloatMeetingWindow: React.FC<MeetingViewProps> = React.memo(
</ErrorBoundary>
<div className="folder-btn" onClick={() => setFolder(!folder)}>
{folder ? '展开' : '收起'}
<div>
<SpeakerNames />
<span style={{ marginLeft: 4 }}>{folder ? '展开' : '收起'}</span>
</div>
</div>
</FloatWindow>
);

@ -9,4 +9,12 @@ export const Translate = {
'zh-CN': '下行网络',
'en-US': 'Downlink',
}),
isSpeaking: localTrans({
'zh-CN': '正在发言',
'en-US': 'is Speaking',
}),
nomanSpeaking: localTrans({
'zh-CN': '无人发言',
'en-US': 'No one Speaking',
}),
};

@ -164,6 +164,8 @@ declare module '@capital/common' {
export const showMessageTime: any;
export const joinArray: any;
export const useLocation: any;
export const useNavigate: any;
@ -244,21 +246,6 @@ declare module '@capital/common' {
export const pluginPanelActions: any;
interface BasePluginPanelActionProps {
/**
*
*/
name: string;
/**
*
*/
label: string;
/**
* iconify
*/
icon: string;
}
export const regPluginPanelAction: (
action:
| {
@ -311,6 +298,10 @@ declare module '@capital/common' {
export const regPluginGroupTextPanelExtraMenu: any;
export const pluginUserExtraInfo: any;
export const regUserExtraInfo: any;
export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => {
@ -474,12 +465,22 @@ declare module '@capital/component' {
export const ErrorBoundary: any;
export const UserAvatar: any;
export const UserAvatar: React.FC<{
userId: string;
className?: string;
style?: React.CSSProperties;
size?: 'large' | 'small' | 'default' | number;
}>;
export const UserName: React.FC<{
userId: string;
className?: string;
style?: React.CSSProperties;
}>;
export const Markdown: any;
export const Webview: any;
export const WebviewKeepAlive: any;
}

Loading…
Cancel
Save