From 2747e0945ece59aeff979a355a8175e5b952f1f5 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Tue, 24 Jan 2023 17:49:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=B1=8F=E5=B9=95?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/agora.service.ts | 4 +- .../src/FloatWindow/Controls.tsx | 37 ++++++++- .../src/FloatWindow/MeetingView.tsx | 9 ++- .../src/FloatWindow/VideoView.tsx | 58 ++++++++++++-- .../src/FloatWindow/client.ts | 4 + .../src/FloatWindow/store.ts | 3 +- .../src/FloatWindow/useScreenSharing.ts | 76 +++++++++++++++++++ .../com.msgbyte.agora/src/translate.ts | 18 ++++- 8 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/useScreenSharing.ts diff --git a/server/plugins/com.msgbyte.agora/services/agora.service.ts b/server/plugins/com.msgbyte.agora/services/agora.service.ts index d08682ec..1cb0db2c 100644 --- a/server/plugins/com.msgbyte.agora/services/agora.service.ts +++ b/server/plugins/com.msgbyte.agora/services/agora.service.ts @@ -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 diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/Controls.tsx b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/Controls.tsx index 7392efb9..5b545c5e 100644 --- a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/Controls.tsx +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/Controls.tsx @@ -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 (
+ mute('screensharing')} + /> + = 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') { diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/VideoView.tsx b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/VideoView.tsx index 60549c53..fbe9cc08 100644 --- a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/VideoView.tsx +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/VideoView.tsx @@ -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 ( +
+ +
+ ); + } else { + return ; + } +}); +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 ( + + + {Translate.someoneScreenName} + + ); + } else { + return ; + } +}); +VideViewName.displayName = 'VideViewName'; + export const VideoView: React.FC<{ user: IAgoraRTCRemoteUser; }> = (props) => { @@ -47,13 +93,13 @@ export const VideoView: React.FC<{ return ( - {user.hasVideo ? ( + {user.hasVideo && user.videoTrack ? ( ) : ( - + )} - + ); }; @@ -75,10 +121,10 @@ export const OwnVideoView: React.FC<{}> = React.memo(() => { {mediaPerm.video ? ( ) : ( - + )} - + ); }); diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/client.ts b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/client.ts index d37552bb..23013259 100644 --- a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/client.ts +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/client.ts @@ -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; diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/store.ts b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/store.ts index 86568f2d..88a36d16 100644 --- a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/store.ts +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/store.ts @@ -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((set) => ({ users: [], - mediaPerm: { video: false, audio: false }, + mediaPerm: { video: false, audio: false, screensharing: false }, volumes: [], appendUser: (user: IAgoraRTCRemoteUser) => { set((state) => ({ diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/useScreenSharing.ts b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/useScreenSharing.ts new file mode 100644 index 00000000..89610413 --- /dev/null +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/FloatWindow/useScreenSharing.ts @@ -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, + }; +} diff --git a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/translate.ts b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/translate.ts index 3a164b23..7097c750 100644 --- a/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/translate.ts +++ b/server/plugins/com.msgbyte.agora/web/plugins/com.msgbyte.agora/src/translate.ts @@ -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", + }), };