diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a41615f3c..7ab151454 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: yarn dist:win --${{ matrix.arch }} working-directory: example - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: AgoraRtcNgExample-win-${{ matrix.arch }} path: | @@ -103,7 +103,7 @@ jobs: yarn dist:mac working-directory: example - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: AgoraRtcNgExample-mac-${{ matrix.arch }} path: | diff --git a/example/src/renderer/App.tsx b/example/src/renderer/App.tsx index 01d0a6e08..7c8038fc2 100644 --- a/example/src/renderer/App.tsx +++ b/example/src/renderer/App.tsx @@ -25,6 +25,8 @@ const { Content, Footer, Sider } = Layout; class App extends Component { state = { version: { version: undefined, build: undefined }, + collapsed: false, + showFooter: true, }; componentDidMount() { @@ -38,6 +40,12 @@ class App extends Component { + this.setState({ + collapsed: e, + }) + } style={{ overflow: 'auto', height: '100vh', @@ -81,7 +89,10 @@ class App extends Component { })()} > - + } exact={true} /> @@ -102,9 +113,18 @@ class App extends Component { -
- {`Powered by Agora RTC SDK ${version.version} ${version.build}`} -
+ {this.state.showFooter && ( + + )}
diff --git a/example/src/renderer/components/BaseComponent.tsx b/example/src/renderer/components/BaseComponent.tsx index d8b52a8b7..54aa7ee8a 100644 --- a/example/src/renderer/components/BaseComponent.tsx +++ b/example/src/renderer/components/BaseComponent.tsx @@ -1,3 +1,4 @@ +import { LeftOutlined } from '@ant-design/icons'; import { ErrorCodeType, IRtcEngine, @@ -31,6 +32,7 @@ export interface BaseComponentState { joinChannelSuccess?: boolean; remoteUsers?: number[]; startPreview?: boolean; + hideRightBar?: boolean; } export interface BaseAudioComponentState extends BaseComponentState { @@ -146,7 +148,19 @@ export abstract class BaseComponent< {users ? this.renderUsers() : undefined} - + + { + this.setState({ + hideRightBar: !this.state.hideRightBar, + }); + }} + /> {this.renderChannel()} {configuration ? ( <> diff --git a/example/src/renderer/components/RtcSurfaceView/index.tsx b/example/src/renderer/components/RtcSurfaceView/index.tsx index 0476ce209..820218205 100644 --- a/example/src/renderer/components/RtcSurfaceView/index.tsx +++ b/example/src/renderer/components/RtcSurfaceView/index.tsx @@ -17,6 +17,8 @@ import styles from './index.scss'; interface Props { canvas: VideoCanvas; + containerClass?: string; + videoClass?: string; connection?: RtcConnection; } @@ -132,12 +134,12 @@ export class RtcSurfaceView extends Component { }; render() { - const { canvas } = this.props; + const { canvas, containerClass, videoClass } = this.props; const { uniqueId } = this.state; return (
{ this.setState((preState) => { return { isMirror: !preState.isMirror }; @@ -145,7 +147,7 @@ export class RtcSurfaceView extends Component { }} >
diff --git a/example/src/renderer/components/ui/public.scss b/example/src/renderer/components/ui/public.scss index 8ade10b3c..bec54c065 100644 --- a/example/src/renderer/components/ui/public.scss +++ b/example/src/renderer/components/ui/public.scss @@ -4,6 +4,7 @@ flex-direction: row; overflow: hidden; height: 100%; + position: relative; .card { height: 340px; @@ -27,6 +28,18 @@ flex-direction: column; padding: 16px 16px 0 16px; background-color: white; + position: relative; + &.hide { + flex: 0; + padding: 0 0 0 16px; + } + } + + .rightBarIcon { + position: absolute; + left: 0; + top: 50%; + cursor: pointer; } .rightBarBig { @@ -41,4 +54,52 @@ color: red; } } + + .meetContainer { + position: relative; + .meetMain { + position: absolute; + width: 80%; + left: 0; + top: 0; + .meetSurfaceViewContainer { + height: 500px; + } + .meetSurfaceViewVideo { + height: 500px; + } + } + .meetRight { + position: absolute; + width: 20%; + right: 0; + top: 0; + display: flex; + flex-wrap: wrap; + .meetRenderContainer { + max-width: 70px; + } + } + } + + .meetRenderContainer { + position: relative; + span { + position: absolute; + width: 100%; + z-index: 333; + color: #fff; + } + .meetSurfaceViewContainer { + flex: 1; + height: 150px; + } + + .meetSurfaceViewVideo { + flex: 1; + height: 150px; + background: #000000; + overflow: hidden; + } + } } diff --git a/example/src/renderer/components/ui/public.scss.d.ts b/example/src/renderer/components/ui/public.scss.d.ts index e4db5bd30..1cca097a2 100644 --- a/example/src/renderer/components/ui/public.scss.d.ts +++ b/example/src/renderer/components/ui/public.scss.d.ts @@ -4,8 +4,16 @@ declare namespace PublicScssNamespace { content: string; require: string; rightBar: string; + hide: string; + rightBarIcon: string; rightBarBig: string; screen: string; + meetRenderContainer: string; + meetContainer: string; + meetMain: string; + meetRight: string; + meetSurfaceViewContainer: string; + meetSurfaceViewVideo: string; selectedItem: string; } } diff --git a/example/src/renderer/examples/advanced/Meet/Meet.tsx b/example/src/renderer/examples/advanced/Meet/Meet.tsx new file mode 100644 index 000000000..06e36d20d --- /dev/null +++ b/example/src/renderer/examples/advanced/Meet/Meet.tsx @@ -0,0 +1,1393 @@ +import { + BackgroundBlurDegree, + BackgroundSourceType, + ChannelProfileType, + ClientRoleType, + EncodingPreference, + IRtcEngineEventHandler, + IRtcEngineEx, + LighteningContrastLevel, + LocalVideoStreamReason, + LocalVideoStreamState, + RenderModeType, + RtcConnection, + RtcStats, + ScreenCaptureSourceInfo, + ScreenCaptureSourceType, + ScreenScenarioType, + SimulcastStreamMode, + UserOfflineReasonType, + VideoSourceType, + VideoStreamType, + createAgoraRtcEngine, +} from 'agora-electron-sdk'; +import React, { ReactElement } from 'react'; +import { SketchPicker } from 'react-color'; + +import { + BaseComponent, + BaseVideoComponentState, +} from '../../../components/BaseComponent'; +import { + AgoraButton, + AgoraDivider, + AgoraDropdown, + AgoraImage, + AgoraSlider, + AgoraStyle, + AgoraSwitch, + AgoraText, + AgoraTextInput, + AgoraView, + RtcSurfaceView, +} from '../../../components/ui'; +import Config from '../../../config/agora.config'; +import { enumToItems, getResourcePath } from '../../../utils'; +import { thumbImageBufferToBase64 } from '../../../utils/base64'; +import { askMediaAccess } from '../../../utils/permissions'; + +interface State extends BaseVideoComponentState { + token2: string; + uid2: number; + showLeftView: boolean; + leftViewUid: number; + leftViewSourceType: VideoSourceType; + sources?: ScreenCaptureSourceInfo[]; + targetSource?: ScreenCaptureSourceInfo; + width: number; + height: number; + frameRate: number; + bitrate: number; + camWidth: number; + camHeight: number; + camFrameRate: number; + camBitrate: number; + camEncodingPreference: EncodingPreference; + screenEncodingPreference: EncodingPreference; + screenWidth: number; + screenHeight: number; + screenFrameRate: number; + screenBitrate: number; + captureMouseCursor: boolean; + windowFocus: boolean; + excludeWindowList: number[]; + screenScenarioType: number; + highLightWidth: number; + highLightColor: number; + enableHighLight: boolean; + startScreenCapture: boolean; + publishScreenCapture: boolean; + remoteRenderLimit: number; + remoteUserUid: number; + videoStreamType: number; + lighteningContrastLevel: LighteningContrastLevel; + lighteningLevel: number; + smoothnessLevel: number; + rednessLevel: number; + sharpnessLevel: number; + enableBeautyEffect: boolean; + background_source_type: BackgroundSourceType; + color: number; + source: string; + blur_degree: BackgroundBlurDegree; + enableVirtualBackground?: boolean; + renderLocalCamera: boolean; +} + +export default class Meet + extends BaseComponent<{}, State> + implements IRtcEngineEventHandler +{ + // @ts-ignore + protected engine?: IRtcEngineEx; + + protected createState(): State { + return { + appId: Config.appId, + enableVideo: true, + channelId: Config.channelId, + token: Config.token, + uid: Config.uid, + joinChannelSuccess: false, + remoteUsers: [], + startPreview: false, + token2: '', + uid2: 0, + showLeftView: false, + leftViewUid: 0, + leftViewSourceType: VideoSourceType.VideoSourceCamera, + sources: [], + targetSource: undefined, + width: 1920, + height: 1080, + frameRate: 15, + bitrate: 0, + camWidth: 320, + camHeight: 180, + camFrameRate: 10, + camBitrate: 200, + camEncodingPreference: EncodingPreference.PreferHardware, + screenEncodingPreference: EncodingPreference.PreferSoftware, + screenWidth: 320, + screenHeight: 180, + screenFrameRate: 10, + screenBitrate: 200, + captureMouseCursor: true, + windowFocus: false, + excludeWindowList: [], + screenScenarioType: ScreenScenarioType.ScreenScenarioDocument, + highLightWidth: 0, + highLightColor: 0xff8cbf26, + enableHighLight: false, + startScreenCapture: false, + publishScreenCapture: false, + remoteRenderLimit: 4, + remoteUserUid: 0, + videoStreamType: VideoStreamType.VideoStreamLow, + lighteningContrastLevel: LighteningContrastLevel.LighteningContrastLow, + lighteningLevel: 0.5, + smoothnessLevel: 0.5, + rednessLevel: 0.5, + sharpnessLevel: 0.5, + enableBeautyEffect: false, + background_source_type: BackgroundSourceType.BackgroundImg, + color: 0xffaabb, + source: getResourcePath('agora-logo.png'), + blur_degree: BackgroundBlurDegree.BlurDegreeHigh, + renderLocalCamera: true, + }; + } + + /** + * Step 1: initRtcEngine + */ + protected async initRtcEngine() { + const { appId } = this.state; + if (!appId) { + this.error(`appId is invalid`); + } + + this.engine = createAgoraRtcEngine() as IRtcEngineEx; + this.engine.initialize({ + appId, + logConfig: { filePath: Config.logFilePath }, + // Should use ChannelProfileLiveBroadcasting on most of cases + channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting, + }); + this.engine.registerEventHandler(this); + + // Need granted the microphone and camera permission + await askMediaAccess(['microphone', 'camera', 'screen']); + + // Need to enable video on this case + // If you only call `enableAudio`, only relay the audio stream to the target channel + this.engine.enableVideo(); + + this.getScreenCaptureSources(); + + // this.engine.setParameters( + // JSON.stringify({ 'engine.video.hw_decoder_provider': 'qsv' }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'rtc.video.default_hw_decoder_thres': 921600 }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'rtc.video.enable_pvc': false }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'che.video.brightness_detection_enable': false }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'che.video.videoCodecIndex': 1 }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'rtc.video.enable_sr': { mode: 2, enabled: false } }) + // ); + // this.engine.setParameters( + // JSON.stringify({ 'che.video.h265_screen_enable': 0 }) + // ); + // this.engine?.setScreenCaptureScenario( + // ScreenScenarioType.ScreenScenarioDocument + // ); + } + + setVideoEncoderConfiguration = () => { + const { + camWidth, + camHeight, + camBitrate, + camFrameRate, + camEncodingPreference, + } = this.state; + + this.engine?.setVideoEncoderConfiguration({ + dimensions: { + width: camWidth, + height: camHeight, + }, + frameRate: camFrameRate, + bitrate: camBitrate, + advanceOptions: { + encodingPreference: camEncodingPreference, + }, + }); + }; + + setVideoEncoderConfigurationEx = () => { + const { + uid2, + channelId, + screenWidth, + screenHeight, + screenBitrate, + screenFrameRate, + screenEncodingPreference, + } = this.state; + + if (!channelId) { + this.error('channelId is invalid'); + return; + } + if (uid2 <= 0) { + this.error('uid2 is invalid'); + return; + } + + this.engine?.setVideoEncoderConfigurationEx( + { + dimensions: { + width: screenWidth, + height: screenHeight, + }, + frameRate: screenFrameRate, + bitrate: screenBitrate, + advanceOptions: { + encodingPreference: screenEncodingPreference, + }, + }, + { localUid: uid2, channelId: channelId } + ); + }; + + setCameraStreamDualStreamMode = () => { + const { camWidth, camHeight, camBitrate, camFrameRate } = this.state; + + this.engine?.setDualStreamMode(SimulcastStreamMode.EnableSimulcastStream, { + dimensions: { + width: camWidth, + height: camHeight, + }, + framerate: camFrameRate, + kBitrate: camBitrate, + }); + }; + setScreenStreamDualStreamMode = () => { + const { + uid2, + channelId, + screenWidth, + screenHeight, + screenBitrate, + screenFrameRate, + } = this.state; + + if (!channelId) { + this.error('channelId is invalid'); + return; + } + if (uid2 <= 0) { + this.error('uid2 is invalid'); + return; + } + + this.engine?.setDualStreamModeEx( + SimulcastStreamMode.EnableSimulcastStream, + { + dimensions: { + width: screenWidth, + height: screenHeight, + }, + framerate: screenFrameRate, + kBitrate: screenBitrate, + }, + { localUid: uid2, channelId: channelId } + ); + }; + + handleStartPreview = () => { + this.engine?.startPreview(); + this.setState({ startPreview: true }); + }; + + handleStopPreview = () => { + this.engine?.stopPreview(); + this.setState({ startPreview: false }); + }; + + /** + * Step 2: joinChannel + */ + protected joinChannel() { + const { channelId, token, uid } = this.state; + if (!channelId) { + this.error('channelId is invalid'); + return; + } + if (uid < 0) { + this.error('uid is invalid'); + return; + } + + // start joining channel + // 1. Users can only see each other after they join the + // same channel successfully using the same app id. + // 2. If app certificate is turned on at dashboard, token is needed + // when joining channel. The channel name and uid used to calculate + // the token has to match the ones used for channel join + this.engine?.joinChannel(token, channelId, uid, { + // Make myself as the broadcaster to send stream to remote + clientRoleType: ClientRoleType.ClientRoleBroadcaster, + }); + + this.setCameraStreamDualStreamMode(); + } + + /** + * Step 3-1: getScreenCaptureSources + */ + getScreenCaptureSources = () => { + const sources = this.engine?.getScreenCaptureSources( + { width: 1920, height: 1080 }, + { width: 64, height: 64 }, + true + ); + this.setState({ + sources, + targetSource: sources?.at(0), + }); + }; + + /** + * Step 3-2: startScreenCapture + */ + startScreenCapture = () => { + const { + targetSource, + width, + height, + frameRate, + bitrate, + captureMouseCursor, + windowFocus, + excludeWindowList, + highLightWidth, + highLightColor, + enableHighLight, + } = this.state; + + if (!targetSource) { + this.error('targetSource is invalid'); + return; + } + + if ( + targetSource.type === + ScreenCaptureSourceType.ScreencapturesourcetypeScreen + ) { + this.engine?.startScreenCaptureByDisplayId( + targetSource.sourceId!, + { x: 0, y: 0, width: 0, height: 0 }, + { + dimensions: { width, height }, + frameRate, + bitrate, + captureMouseCursor, + excludeWindowList, + excludeWindowCount: excludeWindowList.length, + highLightWidth, + highLightColor, + enableHighLight, + } + ); + } else { + this.engine?.startScreenCaptureByWindowId( + targetSource.sourceId!, + {}, + { + dimensions: { width, height }, + frameRate, + bitrate, + windowFocus, + highLightWidth, + highLightColor, + enableHighLight, + } + ); + } + this.setState({ startScreenCapture: true }); + }; + + /** + * Step 3-2 (Optional): updateScreenCaptureParameters + */ + updateScreenCaptureParameters = () => { + const { + width, + height, + frameRate, + bitrate, + captureMouseCursor, + windowFocus, + excludeWindowList, + highLightWidth, + highLightColor, + enableHighLight, + } = this.state; + this.engine?.updateScreenCaptureParameters({ + dimensions: { width, height }, + frameRate, + bitrate, + captureMouseCursor, + windowFocus, + excludeWindowList, + excludeWindowCount: excludeWindowList.length, + highLightWidth, + highLightColor, + enableHighLight, + }); + }; + + /** + * Step 3-4: publishScreenCapture + */ + publishScreenCapture = () => { + const { channelId, token2, uid2 } = this.state; + if (!channelId) { + this.error('channelId is invalid'); + return; + } + if (uid2 <= 0) { + this.error('uid2 is invalid'); + return; + } + + // publish screen share stream + this.engine?.joinChannelEx( + token2, + { channelId, localUid: uid2 }, + { + autoSubscribeAudio: false, + autoSubscribeVideo: false, + publishMicrophoneTrack: false, + publishCameraTrack: false, + clientRoleType: ClientRoleType.ClientRoleBroadcaster, + publishScreenTrack: true, + publishCustomVideoTrack: false, + publishEncodedVideoTrack: false, + defaultVideoStreamType: VideoStreamType.VideoStreamHigh, + } + ); + + this.setScreenStreamDualStreamMode(); + }; + + /** + * Step 3-5: stopScreenCapture + */ + stopScreenCapture = () => { + this.engine?.stopScreenCapture(); + this.setState({ startScreenCapture: false }); + }; + + /** + * Step 3-6: unpublishScreenCapture + */ + unpublishScreenCapture = () => { + const { channelId, uid2 } = this.state; + this.engine?.leaveChannelEx({ channelId, localUid: uid2 }); + }; + + /** + * Step 4: leaveChannel + */ + protected leaveChannel() { + this.engine?.leaveChannel(); + } + + /** + * Step 5: releaseRtcEngine + */ + protected releaseRtcEngine() { + this.engine?.unregisterEventHandler(this); + this.engine?.release(); + } + + onJoinChannelSuccess(connection: RtcConnection, elapsed: number) { + const { uid2 } = this.state; + if (connection.localUid === uid2) { + this.info( + 'onJoinChannelSuccess', + 'connection', + connection, + 'elapsed', + elapsed + ); + this.setState({ publishScreenCapture: true }); + return; + } + super.onJoinChannelSuccess(connection, elapsed); + } + + onLeaveChannel(connection: RtcConnection, stats: RtcStats) { + this.info('onLeaveChannel', 'connection', connection, 'stats', stats); + const { uid2 } = this.state; + if (connection.localUid === uid2) { + this.setState({ publishScreenCapture: false }); + return; + } + const state = this.createState(); + delete state.sources; + delete state.targetSource; + this.setState(state); + } + + onUserJoined(connection: RtcConnection, remoteUid: number, elapsed: number) { + const { uid2 } = this.state; + if (connection.localUid === uid2 || remoteUid === uid2) { + // ⚠️ mute the streams from screen sharing + this.engine?.muteRemoteAudioStream(uid2, true); + this.engine?.muteRemoteVideoStream(uid2, true); + return; + } + super.onUserJoined(connection, remoteUid, elapsed); + } + + onUserOffline( + connection: RtcConnection, + remoteUid: number, + reason: UserOfflineReasonType + ) { + const { uid2 } = this.state; + if (connection.localUid === uid2 || remoteUid === uid2) return; + super.onUserOffline(connection, remoteUid, reason); + } + + onLocalVideoStateChanged( + source: VideoSourceType, + state: LocalVideoStreamState, + error: LocalVideoStreamReason + ) { + this.info( + 'onLocalVideoStateChanged', + 'source', + source, + 'state', + state, + 'error', + error + ); + if (source === VideoSourceType.VideoSourceScreen) { + switch (state) { + case LocalVideoStreamState.LocalVideoStreamStateStopped: + case LocalVideoStreamState.LocalVideoStreamStateFailed: + break; + case LocalVideoStreamState.LocalVideoStreamStateCapturing: + case LocalVideoStreamState.LocalVideoStreamStateEncoding: + this.setState({ startScreenCapture: true }); + break; + } + } + } + + enableBeautyEffect = () => { + const { + lighteningContrastLevel, + lighteningLevel, + smoothnessLevel, + rednessLevel, + sharpnessLevel, + } = this.state; + + this.engine?.setBeautyEffectOptions(true, { + lighteningContrastLevel, + lighteningLevel, + smoothnessLevel, + rednessLevel, + sharpnessLevel, + }); + this.setState({ enableBeautyEffect: true }); + }; + + disableBeautyEffect = () => { + this.engine?.setBeautyEffectOptions(false, {}); + this.setState({ enableBeautyEffect: false }); + }; + + protected setScreenScenarioType = () => { + const { screenScenarioType } = this.state; + this.engine?.setScreenCaptureScenario(screenScenarioType); + }; + + enableVirtualBackground = async () => { + const { background_source_type, color, source, blur_degree } = this.state; + if ( + background_source_type === BackgroundSourceType.BackgroundImg && + !source + ) { + this.error('source is invalid'); + return; + } + + this.engine?.enableVirtualBackground( + true, + { + background_source_type, + color, + source, + blur_degree, + }, + {} + ); + this.setState({ enableVirtualBackground: true }); + }; + + disableVirtualBackground = () => { + this.engine?.enableVirtualBackground(false, {}, {}); + this.setState({ enableVirtualBackground: false }); + }; + + protected renderUsers(): ReactElement | undefined { + const { + startPreview, + joinChannelSuccess, + remoteUsers, + remoteRenderLimit, + renderLocalCamera, + showLeftView, + leftViewUid, + leftViewSourceType, + } = this.state; + return ( +
+
+ {showLeftView && ( +
+ +
+ )} +
+
+ {(!!startPreview || joinChannelSuccess) && renderLocalCamera ? ( +
+ {`${'local-cam'} - ${ + VideoSourceType.VideoSourceCamera + }`} + +
+ ) : undefined} + {(remoteUsers.slice(0, remoteRenderLimit) ?? []) + .filter((uid) => { + return uid != leftViewUid; + }) + .map((uid, index) => { + return ( +
+ {`${'remote-cam'} - ${ + VideoSourceType.VideoSourceRemote + }`} + +
+ ); + })} +
+
+ ); + } + + protected renderChannel(): ReactElement | undefined { + const { + channelId, + startPreview, + joinChannelSuccess, + camEncodingPreference, + showLeftView, + leftViewUid, + leftViewSourceType, + renderLocalCamera, + } = this.state; + return ( + <> + <> + { + this.setState({ channelId: text }); + }} + placeholder={`channelId`} + value={channelId} + /> + leftViewUid + { + if (isNaN(+text)) return; + this.setState({ + leftViewUid: + text === '' ? this.createState().leftViewUid : +text, + }); + }} + numberKeyboard={true} + placeholder={`leftViewUid`} + value={leftViewUid > 0 ? leftViewUid.toString() : ''} + /> + { + this.setState({ leftViewSourceType: value }); + }} + /> + { + this.setState({ + showLeftView: !showLeftView, + }); + }} + /> + { + joinChannelSuccess ? this.leaveChannel() : this.joinChannel(); + }} + /> + + + { + if (isNaN(+text)) return; + this.setState({ + camWidth: text === '' ? this.createState().camWidth : +text, + }); + }} + numberKeyboard={true} + placeholder={`camWidth (defaults: ${this.createState().camWidth})`} + /> + { + if (isNaN(+text)) return; + this.setState({ + camHeight: text === '' ? this.createState().camHeight : +text, + }); + }} + numberKeyboard={true} + placeholder={`camHeight (defaults: ${this.createState().camHeight})`} + /> + { + if (isNaN(+text)) return; + this.setState({ + camFrameRate: + text === '' ? this.createState().camFrameRate : +text, + }); + }} + numberKeyboard={true} + placeholder={`camFrameRate (defaults: ${ + this.createState().camFrameRate + })`} + /> + { + if (isNaN(+text)) return; + this.setState({ + camBitrate: text === '' ? this.createState().camBitrate : +text, + }); + }} + numberKeyboard={true} + placeholder={`camBitrate (defaults: ${ + this.createState().camBitrate + })`} + /> + { + this.setState({ camEncodingPreference: value }); + }} + /> + + + { + this.setState({ renderLocalCamera: value }); + }} + /> + + ); + } + + protected renderConfiguration(): ReactElement | undefined { + const { + sources, + targetSource, + uid2, + channelId, + captureMouseCursor, + windowFocus, + excludeWindowList, + highLightWidth, + highLightColor, + enableHighLight, + publishScreenCapture, + screenScenarioType, + joinChannelSuccess, + remoteUserUid, + videoStreamType, + lighteningContrastLevel, + lighteningLevel, + smoothnessLevel, + screenEncodingPreference, + rednessLevel, + sharpnessLevel, + enableBeautyEffect, + background_source_type, + color, + source, + blur_degree, + enableVirtualBackground, + startScreenCapture, + } = this.state; + return ( + <> + { + return { + value: value.sourceId!, + label: value.sourceName!, + }; + })} + value={targetSource?.sourceId} + onValueChange={(value, index) => { + this.setState((preState) => { + return { targetSource: preState.sources?.at(index) }; + }); + }} + /> + {targetSource ? ( + + ) : undefined} + { + if (text === '') { + text = '0'; + } + if (isNaN(+text)) return; + this.setState({ + remoteRenderLimit: + text === '' ? this.createState().remoteRenderLimit : +text, + }); + if (joinChannelSuccess) { + this.state.remoteUsers.map((uid, index) => { + if (index + 1 > +text) { + this.engine?.muteRemoteVideoStream(uid, true); + this.engine?.muteRemoteAudioStream(uid, true); + } else { + this.engine?.muteRemoteVideoStream(uid, false); + this.engine?.muteRemoteAudioStream(uid, false); + } + }); + } + }} + numberKeyboard={true} + placeholder={`remoteRenderLimit (defaults: ${ + this.createState().remoteRenderLimit + })`} + /> + { + if (isNaN(+text)) return; + this.setState({ + uid2: text === '' ? this.createState().uid2 : +text, + }); + }} + numberKeyboard={true} + placeholder={`uid2 (must > 0)`} + value={uid2 > 0 ? uid2.toString() : ''} + /> + + { + if (isNaN(+text)) return; + this.setState({ + width: text === '' ? this.createState().width : +text, + }); + }} + numberKeyboard={true} + placeholder={`width (defaults: ${this.createState().width})`} + /> + { + if (isNaN(+text)) return; + this.setState({ + height: text === '' ? this.createState().height : +text, + }); + }} + numberKeyboard={true} + placeholder={`height (defaults: ${this.createState().height})`} + /> + + { + if (isNaN(+text)) return; + this.setState({ + frameRate: text === '' ? this.createState().frameRate : +text, + }); + }} + numberKeyboard={true} + placeholder={`frameRate (defaults: ${this.createState().frameRate})`} + /> + { + if (isNaN(+text)) return; + this.setState({ + bitrate: text === '' ? this.createState().bitrate : +text, + }); + }} + numberKeyboard={true} + placeholder={`bitrate (defaults: ${this.createState().bitrate})`} + /> + {targetSource?.type === + ScreenCaptureSourceType.ScreencapturesourcetypeScreen ? ( + <> + { + this.setState({ captureMouseCursor: value }); + }} + /> + + ) : undefined} + {targetSource?.type === + ScreenCaptureSourceType.ScreencapturesourcetypeWindow ? ( + <> + { + this.setState({ windowFocus: value }); + }} + /> + + ) : undefined} + {targetSource?.type === + ScreenCaptureSourceType.ScreencapturesourcetypeScreen ? ( + <> + + value.type === + ScreenCaptureSourceType.ScreencapturesourcetypeWindow + ) + .map((value) => { + return { + value: value.sourceId!, + label: value.sourceName!, + }; + })} + value={excludeWindowList} + onValueChange={(value, index) => { + if (excludeWindowList.indexOf(value) === -1) { + this.setState((preState) => { + return { + excludeWindowList: [...preState.excludeWindowList, value], + }; + }); + } else { + this.setState((preState) => { + return { + excludeWindowList: preState.excludeWindowList.filter( + (v) => v !== value + ), + }; + }); + } + }} + /> + + ) : undefined} + { + this.setState({ enableHighLight: value }); + }} + /> + {enableHighLight ? ( + <> + + { + this.setState({ + highLightWidth: value, + }); + }} + /> + + { + const { a = 1, r, g, b } = color.rgb; + const argbHex = + `${((a * 255) | (1 << 8)).toString(16).slice(1)}` + + `${(r | (1 << 8)).toString(16).slice(1)}` + + `${(g | (1 << 8)).toString(16).slice(1)}` + + `${(b | (1 << 8)).toString(16).slice(1)}`; + console.log( + 'onChangeComplete', + color.hex, + `#${argbHex}`, + +`0x${argbHex}`, + color + ); + this.setState({ + highLightColor: +`0x${argbHex}`, + }); + }} + color={(function () { + const argb = highLightColor?.toString(16); + const rgba = `${argb.slice(2)}` + `${argb.slice(0, 2)}`; + console.log('argb', `#${argb}`, 'rgba', `#${rgba}`); + return `#${rgba}`; + })()} + /> + + ) : undefined} + { + this.engine?.setScreenCaptureScenario(value); + }} + /> + { + if (isNaN(+text)) return; + this.setState({ + screenWidth: text === '' ? this.createState().screenWidth : +text, + }); + }} + numberKeyboard={true} + placeholder={`screenWidth (defaults: ${ + this.createState().screenWidth + })`} + /> + { + if (isNaN(+text)) return; + this.setState({ + screenHeight: + text === '' ? this.createState().screenHeight : +text, + }); + }} + numberKeyboard={true} + placeholder={`screenHeight (defaults: ${ + this.createState().screenHeight + })`} + /> + { + if (isNaN(+text)) return; + this.setState({ + screenFrameRate: + text === '' ? this.createState().screenFrameRate : +text, + }); + }} + numberKeyboard={true} + placeholder={`screenFrameRate (defaults: ${ + this.createState().screenFrameRate + })`} + /> + { + if (isNaN(+text)) return; + this.setState({ + screenBitrate: + text === '' ? this.createState().screenBitrate : +text, + }); + }} + numberKeyboard={true} + placeholder={`screenBitrate (defaults: ${ + this.createState().screenBitrate + })`} + /> + { + this.setState({ screenEncodingPreference: value }); + }} + /> + + + <> + + + + + <> + + { + if (isNaN(+text)) return; + this.setState({ + remoteUserUid: + text === '' ? this.createState().remoteUserUid : +text, + }); + }} + numberKeyboard={true} + placeholder={`remoteUserUid`} + /> + { + this.setState({ videoStreamType: value }); + }} + /> + { + this.engine?.setRemoteVideoStreamType( + remoteUserUid, + videoStreamType + ); + }} + /> + { + this.engine?.setRemoteVideoStreamTypeEx( + this.state.remoteUserUid, + videoStreamType, + { channelId: channelId, localUid: uid2 } + ); + }} + /> + + + <> + { + this.setState({ lighteningContrastLevel: value }); + }} + /> + { + this.setState({ + lighteningLevel: value, + }); + }} + /> + { + this.setState({ + smoothnessLevel: value, + }); + }} + /> + { + this.setState({ + rednessLevel: value, + }); + }} + /> + { + this.setState({ + sharpnessLevel: value, + }); + }} + /> + + + <> + + { + this.setState({ background_source_type: value }); + }} + /> + {background_source_type === BackgroundSourceType.BackgroundColor ? ( + { + this.setState({ + color: +hex.replace('#', '0x'), + }); + }} + color={`#${color?.toString(16)}`} + /> + ) : undefined} + { + this.setState({ + source: text, + }); + }} + placeholder={'source'} + value={source} + /> + { + this.setState({ blur_degree: value }); + }} + /> + + + + ); + } +} diff --git a/example/src/renderer/examples/advanced/index.ts b/example/src/renderer/examples/advanced/index.ts index b97810c39..bed20aa93 100644 --- a/example/src/renderer/examples/advanced/index.ts +++ b/example/src/renderer/examples/advanced/index.ts @@ -14,6 +14,7 @@ import LocalSpatialAudioEngine from './LocalSpatialAudioEngine/LocalSpatialAudio import LocalVideoTranscoder from './LocalVideoTranscoder/LocalVideoTranscoder'; import MediaPlayer from './MediaPlayer/MediaPlayer'; import MediaRecorder from './MediaRecorder/MediaRecorder'; +import Meet from './Meet/Meet'; import MusicContentCenter from './MusicContentCenter/MusicContentCenter'; import PlayEffect from './PlayEffect/PlayEffect'; import ProcessVideoRawData from './ProcessVideoRawData/ProcessVideoRawData'; @@ -162,6 +163,10 @@ const Advanced = { name: 'VoiceChanger', component: VoiceChanger, }, + { + name: 'Meet', + component: Meet, + }, ], }; diff --git a/example/src/renderer/utils/index.ts b/example/src/renderer/utils/index.ts index a088017c1..143f232d0 100644 --- a/example/src/renderer/utils/index.ts +++ b/example/src/renderer/utils/index.ts @@ -40,12 +40,12 @@ export const arrayToItems = (array: any[]): AgoraDropdownItem[] => { }; export const enumToItems = (enumType: any): AgoraDropdownItem[] => { - const items = Object.values(enumType); - const keys = items.filter((v) => typeof v === 'string') as string[]; - const values = items.filter((v) => typeof v === 'number') as number[]; - return keys.map((value, index) => ({ - label: value, - value: values[index], + const entries = Object.entries(enumType); + const items = entries.filter(([, value]) => typeof value === 'number'); + items.sort((a: any, b: any) => a[1] - b[1]); + return items.map(([key, value]) => ({ + label: key, + value: value, })); }; diff --git a/example/yarn.lock b/example/yarn.lock index 8e6838ca3..72133b062 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1792,10 +1792,10 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -agora-electron-sdk@4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/agora-electron-sdk/-/agora-electron-sdk-4.3.2.tgz#943b50078e75f0275f89d5d4615bfb23f6b053a0" - integrity sha512-DKB/VuGT0e/IEkHeWC5mfEE+FbN6vOsyDDMMErukLuka8zgVrZE/qDwXK9+YZOzA9+jqKkDHuB/YzmfG5bnR6Q== +agora-electron-sdk@4.3.2-build.11-rc.1: + version "4.3.2-build.11-rc.1" + resolved "https://registry.npmjs.org/agora-electron-sdk/-/agora-electron-sdk-4.3.2-build.11-rc.1.tgz#922dea76cd569d82c52bf4fd96cd1168e01d638f" + integrity sha512-pFRbTqjcakHwuZMuvrXzcLAL9wT1hqTZVPK/sb85e0ikk3Wo1UxHTqCDnzu6BDBtPxAURtdGL/90FrQLCALW+A== dependencies: buffer "^6.0.3" cross-env "^7.0.3"