feat: 增加屏幕共享功能

feat/uniplus
moonrailgun 2 years ago
parent f13478a984
commit 2747e0945e

@ -93,6 +93,7 @@ class AgoraService extends TcService {
this.registerAction('generateJoinInfo', this.generateJoinInfo, {
params: {
channelName: 'string',
userId: { type: 'string', optional: true },
},
});
this.registerAction('getChannelUserList', this.getChannelUserList, {
@ -135,6 +136,7 @@ class AgoraService extends TcService {
generateJoinInfo(
ctx: TcContext<{
channelName: string;
userId?: string;
}>
) {
const { channelName } = ctx.params;
@ -147,7 +149,7 @@ class AgoraService extends TcService {
const role = RtcRole.PUBLISHER;
const userId = ctx.meta.userId;
const userId = ctx.params.userId ?? ctx.meta.userId;
const tokenExpirationInSecond = 3600; // 1h
const privilegeExpirationInSecond = 3600; // 1h

@ -1,5 +1,6 @@
import { useAsyncFn } from '@capital/common';
import { IconBtn } from '@capital/component';
import { useMemoizedFn } from 'ahooks';
import React from 'react';
import { Translate } from '../translate';
import {
@ -8,16 +9,21 @@ import {
createCameraVideoTrack,
} from './client';
import { useMeetingStore } from './store';
import { useScreenSharing } from './useScreenSharing';
import { getClientLocalTrack } from './utils';
/**
*
*/
export const Controls: React.FC<{
onClose: () => void;
}> = React.memo((props) => {
const client = useClient();
const { startScreenSharing, stopScreenSharing } = useScreenSharing();
const mediaPerm = useMeetingStore((state) => state.mediaPerm);
const [{ loading }, mute] = useAsyncFn(
async (type: 'audio' | 'video') => {
useMemoizedFn(async (type: 'audio' | 'video' | 'screensharing') => {
if (type === 'audio') {
if (mediaPerm.audio === true) {
const track = getClientLocalTrack(client, 'audio');
@ -42,9 +48,17 @@ export const Controls: React.FC<{
}
useMeetingStore.getState().setMediaPerm({ video: !mediaPerm.video });
} else if (type === 'screensharing') {
if (mediaPerm.screensharing === true) {
// 关闭屏幕共享
await stopScreenSharing();
} else {
// 开始屏幕共享
await startScreenSharing();
}
}
},
[client, mediaPerm]
}),
[]
);
const leaveChannel = async () => {
@ -60,6 +74,23 @@ export const Controls: React.FC<{
return (
<div className="controller">
<IconBtn
icon={
mediaPerm.screensharing
? 'mdi:projector-screen-outline'
: 'mdi:projector-screen-off-outline'
}
title={
mediaPerm.screensharing
? Translate.closeScreensharing
: Translate.openScreensharing
}
active={mediaPerm.screensharing}
disabled={loading}
size="large"
onClick={() => mute('screensharing')}
/>
<IconBtn
icon={mediaPerm.video ? 'mdi:video' : 'mdi:video-off'}
title={mediaPerm.video ? Translate.closeCamera : Translate.openCamera}

@ -5,13 +5,13 @@ import { Videos } from './Videos';
import { Controls } from './Controls';
import { LoadingSpinner } from '@capital/component';
import { useMemoizedFn } from 'ahooks';
import { request } from '../request';
import styled from 'styled-components';
import { useMeetingStore } from './store';
import { NetworkStats } from './NetworkStats';
import _once from 'lodash/once';
import type { IAgoraRTCClient } from 'agora-rtc-react';
import { Translate } from '../translate';
import { request } from '../request';
const Root = styled.div`
.body {
@ -43,7 +43,14 @@ export const MeetingView: React.FC<MeetingViewProps> = React.memo((props) => {
const initedRef = useRef(false);
const init = useMemoizedFn(async (channelName: string) => {
const { _id } = await getJWTUserInfo();
client.on('user-published', async (user, mediaType) => {
if (String(user.uid).startsWith(_id)) {
// 不监听自身
return;
}
await client.subscribe(user, mediaType);
console.log('subscribe success');
if (mediaType === 'audio') {

@ -1,7 +1,8 @@
import { UserAvatar, UserName } from '@capital/component';
import { Icon, UserAvatar, UserName } from '@capital/component';
import { AgoraVideoPlayer, IAgoraRTCRemoteUser } from 'agora-rtc-react';
import React from 'react';
import styled from 'styled-components';
import { Translate } from '../translate';
import { useClient } from './client';
import { useMeetingStore } from './store';
import { getClientLocalTrack } from './utils';
@ -36,9 +37,54 @@ const Root = styled.div<{
left: 0;
bottom: 0;
padding: 4px 8px;
color: white;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
.screen-icon {
width: 96px;
height: 96px;
font-size: 96px;
}
`;
/**
* Icon
*/
const VideViewIcon: React.FC<{ uid: string }> = React.memo(({ uid }) => {
if (uid.endsWith('_screen')) {
// 是屏幕共享
return (
<div className="screen-icon">
<Icon icon="mdi:projector-screen-outline" />
</div>
);
} else {
return <UserAvatar size={96} userId={uid} />;
}
});
VideViewIcon.displayName = 'VideViewIcon';
/**
*
*/
const VideViewName: React.FC<{ uid: string }> = React.memo(({ uid }) => {
if (uid.endsWith('_screen')) {
const userId = uid.substring(0, uid.length - '_screen'.length);
return (
<span className="name">
<UserName userId={userId} />
{Translate.someoneScreenName}
</span>
);
} else {
return <UserName className="name" userId={uid} />;
}
});
VideViewName.displayName = 'VideViewName';
export const VideoView: React.FC<{
user: IAgoraRTCRemoteUser;
}> = (props) => {
@ -47,13 +93,13 @@ export const VideoView: React.FC<{
return (
<Root active={active}>
{user.hasVideo ? (
{user.hasVideo && user.videoTrack ? (
<AgoraVideoPlayer className="player" videoTrack={user.videoTrack} />
) : (
<UserAvatar size={96} userId={String(user.uid)} />
<VideViewIcon uid={String(user.uid)} />
)}
<UserName className="name" userId={String(user.uid)} />
<VideViewName uid={String(user.uid)} />
</Root>
);
};
@ -75,10 +121,10 @@ export const OwnVideoView: React.FC<{}> = React.memo(() => {
{mediaPerm.video ? (
<AgoraVideoPlayer className="player" videoTrack={videoTrack} />
) : (
<UserAvatar size={96} userId={String(client.uid)} />
<VideViewIcon uid={String(client.uid)} />
)}
<UserName className="name" userId={String(client.uid)} />
<VideViewName uid={String(client.uid)} />
</Root>
);
});

@ -8,3 +8,7 @@ const config: ClientConfig = {
export const useClient = createClient(config);
export const createCameraVideoTrack = AgoraRTC.createCameraVideoTrack;
export const createMicrophoneAudioTrack = AgoraRTC.createMicrophoneAudioTrack;
// 屏幕共享
export const useScreenSharingClient = createClient(config);
export const createScreenVideoTrack = AgoraRTC.createScreenVideoTrack;

@ -4,6 +4,7 @@ import create from 'zustand';
interface MediaPerm {
video: boolean;
audio: boolean;
screensharing: boolean;
}
interface MeetingState {
@ -38,7 +39,7 @@ interface MeetingState {
export const useMeetingStore = create<MeetingState>((set) => ({
users: [],
mediaPerm: { video: false, audio: false },
mediaPerm: { video: false, audio: false, screensharing: false },
volumes: [],
appendUser: (user: IAgoraRTCRemoteUser) => {
set((state) => ({

@ -0,0 +1,76 @@
import { getJWTUserInfo } from '@capital/common';
import type { ILocalVideoTrack } from 'agora-rtc-react';
import { useMemoizedFn } from 'ahooks';
import { useEffect } from 'react';
import { request } from '../request';
import {
createScreenVideoTrack,
useClient,
useScreenSharingClient,
} from './client';
import { useMeetingStore } from './store';
/**
*
*/
export function useScreenSharing() {
const client = useClient();
const screenSharingClient = useScreenSharingClient();
useEffect(() => {
() => {
screenSharingClient.leave();
};
}, []);
const startScreenSharing = useMemoizedFn(async () => {
if (!client.channelName) {
return;
}
const track = await createScreenVideoTrack(
{
optimizationMode: 'detail',
},
'auto'
);
let t: ILocalVideoTrack;
if (Array.isArray(track)) {
t = track[0];
} else {
t = track;
}
t.on('track-ended', () => {
// 画面断开时自动触发停止共享(用户点击停止共享按钮)
stopScreenSharing();
});
const channelName = client.channelName;
const { _id } = await getJWTUserInfo();
const uid = _id + '_screen';
const { data } = await request.post('generateJoinInfo', {
channelName,
userId: uid,
});
const { appId, token } = data ?? {};
await screenSharingClient.join(appId, channelName, token, uid);
await screenSharingClient.publish(track);
useMeetingStore.getState().setMediaPerm({ screensharing: true });
});
const stopScreenSharing = useMemoizedFn(async () => {
screenSharingClient.localTracks.forEach((t) => t.close());
await screenSharingClient.unpublish();
await screenSharingClient.leave();
useMeetingStore.getState().setMediaPerm({ screensharing: false });
});
return {
startScreenSharing,
stopScreenSharing,
};
}

@ -18,11 +18,11 @@ export const Translate = {
'en-US': 'No one Speaking',
}),
startCall: localTrans({
'zh-CN': '发起通话',
'en-US': 'Start Call',
'zh-CN': '发起/加入通话',
'en-US': 'Start/Join Call',
}),
startCallContent: localTrans({
'zh-CN': '是否通过声网插件在当前会话开启音视频通讯?',
'zh-CN': '是否通过声网插件在当前会话开启/加入音视频通讯?',
'en-US':
'Do you want to enable audio and video communication in the current session through the Agora plugin?',
}),
@ -63,4 +63,16 @@ export const Translate = {
'zh-CN': '关闭麦克风',
'en-US': 'Close Mic',
}),
openScreensharing: localTrans({
'zh-CN': '开启屏幕共享',
'en-US': 'Open Screensharing',
}),
closeScreensharing: localTrans({
'zh-CN': '关闭屏幕共享',
'en-US': 'Close Screensharing',
}),
someoneScreenName: localTrans({
'zh-CN': ' 的屏幕',
'en-US': "'s Screen",
}),
};

Loading…
Cancel
Save