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",
+ }),
};