├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── feature_request.yml │ └── questions.yml ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── build.yml │ ├── gitleak.yml │ ├── pr.yml │ ├── publish.yml │ ├── publishCDN.yml │ ├── sync.yml │ ├── typedoc.yml │ ├── update-doc.yml │ └── upgrade-dep.yml ├── .gitignore ├── .gitleaks.toml ├── .husky ├── commit-msg └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── cspell.json ├── examples ├── basic │ ├── .env.example │ ├── README.md │ ├── index.html │ ├── index.scss │ ├── main.tsx │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AutoLayout.tsx │ │ │ ├── Client.tsx │ │ │ ├── Container.tsx │ │ │ ├── Label.tsx │ │ │ ├── LocalTracks.tsx │ │ │ ├── MediaControl.tsx │ │ │ ├── RemoteUsers.tsx │ │ │ ├── Room.tsx │ │ │ ├── ScreenShare.tsx │ │ │ ├── ToolBox.tsx │ │ │ ├── UsersInfo.tsx │ │ │ └── index.ts │ │ ├── pages │ │ │ ├── advanced │ │ │ │ ├── index.ts │ │ │ │ ├── multi-channel │ │ │ │ │ ├── index.scss │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── store.ts │ │ │ │ ├── share-screen │ │ │ │ │ └── index.tsx │ │ │ │ └── switch-layout │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.tsx │ │ │ ├── basic │ │ │ │ ├── index.ts │ │ │ │ └── overview │ │ │ │ │ └── index.tsx │ │ │ ├── component │ │ │ │ ├── LocalAudioTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── LocalUser │ │ │ │ │ └── index.tsx │ │ │ │ ├── LocalVideoTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── RemoteAudioTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── RemoteUser │ │ │ │ │ └── index.tsx │ │ │ │ ├── RemoteVideoTrack │ │ │ │ │ └── index.tsx │ │ │ │ └── index.ts │ │ │ ├── hook │ │ │ │ ├── index.ts │ │ │ │ ├── useAutoPlayAudioTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── useAutoPlayVideoTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── useClientEvent │ │ │ │ │ └── index.tsx │ │ │ │ ├── useConnectionState │ │ │ │ │ └── index.tsx │ │ │ │ ├── useCurrentUID │ │ │ │ │ └── index.tsx │ │ │ │ ├── useIsConnected │ │ │ │ │ └── index.tsx │ │ │ │ ├── useJoin │ │ │ │ │ └── index.tsx │ │ │ │ ├── useLocalScreenTrack │ │ │ │ │ └── index.tsx │ │ │ │ ├── useNetworkQuality │ │ │ │ │ └── index.tsx │ │ │ │ ├── usePublish │ │ │ │ │ └── index.tsx │ │ │ │ └── useRemoteUsers │ │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ └── setting │ │ │ │ └── index.tsx │ │ └── utils │ │ │ ├── const.ts │ │ │ ├── fake.ts │ │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite-env.d.ts │ └── vite.config.ts └── mobx │ ├── .env.example │ ├── README.md │ ├── index.html │ ├── index.scss │ ├── package.json │ ├── src │ ├── components │ │ ├── App.tsx │ │ ├── Controls.tsx │ │ ├── Home.tsx │ │ ├── LocalUser.tsx │ │ ├── Room.tsx │ │ ├── RoomInfo.tsx │ │ ├── ShareScreenTracks.tsx │ │ ├── Users.tsx │ │ └── buttons.tsx │ ├── constants.ts │ ├── global.d.ts │ ├── main.tsx │ ├── stores │ │ ├── app.store.ts │ │ ├── local-user.store.ts │ │ ├── remote-user.store.ts │ │ ├── share-screen.store.ts │ │ └── users.store.ts │ ├── styles.css │ ├── utils.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── packages ├── agora-rtc-react-ui │ ├── .storybook │ │ ├── main.js │ │ ├── preview-head.html │ │ └── preview.js │ ├── package.json │ ├── src │ │ ├── assets │ │ │ └── UserControl.css │ │ ├── components │ │ │ ├── CameraControl.tsx │ │ │ ├── CameraVideoTrack.tsx │ │ │ ├── LocalMicrophoneAndCameraUser.tsx │ │ │ ├── MicControl.tsx │ │ │ ├── MicrophoneAudioTrack.tsx │ │ │ ├── RemoteVideoPlayer.tsx │ │ │ ├── icons │ │ │ │ ├── SVGCamera.tsx │ │ │ │ ├── SVGCameraMute.tsx │ │ │ │ ├── SVGMicrophone.tsx │ │ │ │ ├── SVGMicrophoneMute.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── stories │ │ │ ├── CameraControl.stories.tsx │ │ │ ├── CameraVideoTrack.stories.tsx │ │ │ ├── Devices.stories.tsx │ │ │ ├── LocalMicrophoneAndCameraUser.stories.tsx │ │ │ ├── MicControl.stories.tsx │ │ │ ├── MicrophoneAudioTrack.stories.tsx │ │ │ └── RemoteVideoPlayer.stories.tsx │ │ └── vite-env.d.ts │ ├── test │ │ ├── CameraVideoTrack.test.tsx │ │ ├── LocalMicrophoneAndCameraUser.test.tsx │ │ ├── MicrophoneAudioTrack.test.tsx │ │ ├── RemoteVideoPlayer.test.tsx │ │ └── setup.tsx │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vite.config.ts ├── agora-rtc-react │ ├── .storybook │ │ ├── main.ts │ │ ├── preview-head.html │ │ └── preview.ts │ ├── docs │ │ ├── components-en.mdx │ │ ├── components.mdx │ │ ├── components │ │ │ ├── AgoraRTCProvider.en-US.mdx │ │ │ ├── AgoraRTCProvider.zh-CN.mdx │ │ │ ├── AgoraRTCScreenShareProvider.en-US.mdx │ │ │ ├── AgoraRTCScreenShareProvider.zh-CN.mdx │ │ │ ├── LocalAudioTrack.en-US.mdx │ │ │ ├── LocalAudioTrack.zh-CN.mdx │ │ │ ├── LocalUser.en-US.mdx │ │ │ ├── LocalUser.zh-CN.mdx │ │ │ ├── LocalVideoTrack.en-US.mdx │ │ │ ├── LocalVideoTrack.zh-CN.mdx │ │ │ ├── RemoteAudioTrack.en-US.mdx │ │ │ ├── RemoteAudioTrack.zh-CN.mdx │ │ │ ├── RemoteUser.en-US.mdx │ │ │ ├── RemoteUser.zh-CN.mdx │ │ │ ├── RemoteVideoTrack.en-US.mdx │ │ │ └── RemoteVideoTrack.zh-CN.mdx │ │ ├── data-types-en.mdx │ │ ├── data-types.mdx │ │ ├── data-types │ │ │ ├── AgoraRTCReactError.en-US.mdx │ │ │ ├── AgoraRTCReactError.zh-CN.mdx │ │ │ ├── JoinOptions.en-US.mdx │ │ │ ├── JoinOptions.zh-CN.mdx │ │ │ ├── NetworkQuality.en-US.mdx │ │ │ └── NetworkQualityEx.zh-CN.mdx │ │ ├── hooks-en.mdx │ │ ├── hooks.mdx │ │ └── hooks │ │ │ ├── useAutoPlayAudioTrack.en-US.mdx │ │ │ ├── useAutoPlayAudioTrack.zh-CN.mdx │ │ │ ├── useAutoPlayVideoTrack.en-US.mdx │ │ │ ├── useAutoPlayVideoTrack.zh-CN.mdx │ │ │ ├── useClientEvent.en-US.mdx │ │ │ ├── useClientEvent.zh-CN.mdx │ │ │ ├── useConnectionState.en-US.mdx │ │ │ ├── useConnectionState.zh-CN.mdx │ │ │ ├── useCurrentUID.en-US.mdx │ │ │ ├── useCurrentUID.zh-CN.mdx │ │ │ ├── useIsConnected.en-US.mdx │ │ │ ├── useIsConnected.zh-CN.mdx │ │ │ ├── useJoin.en-US.mdx │ │ │ ├── useJoin.zh-CN.mdx │ │ │ ├── useLocalCameraTrack.en-US.mdx │ │ │ ├── useLocalCameraTrack.zh-CN.mdx │ │ │ ├── useLocalMicrophoneTrack.en-US.mdx │ │ │ ├── useLocalMicrophoneTrack.zh-CN.mdx │ │ │ ├── useLocalScreenTrack.en-US.mdx │ │ │ ├── useLocalScreenTrack.zh-CN.mdx │ │ │ ├── useNetworkQuality.en-US.mdx │ │ │ ├── useNetworkQuality.zh-CN.mdx │ │ │ ├── usePublish.en-US.mdx │ │ │ ├── usePublish.zh-CN.mdx │ │ │ ├── useRTCClient.en-US.mdx │ │ │ ├── useRTCClient.zh-CN.mdx │ │ │ ├── useRemoteAudioTracks.en-US.mdx │ │ │ ├── useRemoteAudioTracks.zh-CN.mdx │ │ │ ├── useRemoteUserTrack.en-US.mdx │ │ │ ├── useRemoteUserTrack.zh-CN.mdx │ │ │ ├── useRemoteUsers.en-US.mdx │ │ │ ├── useRemoteUsers.zh-CN.mdx │ │ │ ├── useRemoteVideoTracks.en-US.mdx │ │ │ ├── useRemoteVideoTracks.zh-CN.mdx │ │ │ ├── useTrackEvent.en-US.mdx │ │ │ ├── useTrackEvent.zh-CN.mdx │ │ │ ├── useVolumeLevel.en-US.mdx │ │ │ └── useVolumeLevel.zh-CN.mdx │ ├── package.json │ ├── src │ │ ├── assets │ │ │ └── styles.ts │ │ ├── components │ │ │ ├── LocalAudioTrack.tsx │ │ │ ├── LocalUser.tsx │ │ │ ├── LocalVideoTrack.tsx │ │ │ ├── RemoteAudioTrack.tsx │ │ │ ├── RemoteUser.tsx │ │ │ ├── RemoteVideoTrack.tsx │ │ │ ├── TrackBoundary.tsx │ │ │ ├── UserCover.tsx │ │ │ └── index.ts │ │ ├── error.ts │ │ ├── hooks │ │ │ ├── events.ts │ │ │ ├── index.ts │ │ │ ├── tools.ts │ │ │ ├── types.ts │ │ │ ├── useConnectionState.ts │ │ │ ├── useCurrentUID.ts │ │ │ ├── useIsConnected.ts │ │ │ ├── useJoin.ts │ │ │ ├── useLocalCameraTrack.ts │ │ │ ├── useLocalMicrophoneTrack.ts │ │ │ ├── useLocalScreenTrack.ts │ │ │ ├── useNetworkQuality.ts │ │ │ ├── usePublish.ts │ │ │ ├── useRTCClient.tsx │ │ │ ├── useRTCScreenShareClient.tsx │ │ │ ├── useRemoteAudioTracks.ts │ │ │ ├── useRemoteUserTrack.ts │ │ │ ├── useRemoteUsers.ts │ │ │ ├── useRemoteVideoTracks.ts │ │ │ └── useVolumeLevel.ts │ │ ├── index.ts │ │ ├── misc │ │ │ ├── listen.ts │ │ │ ├── store.ts │ │ │ └── utils.ts │ │ ├── rtc.ts │ │ ├── stories │ │ │ ├── LocalAudioTrack.stories.tsx │ │ │ ├── LocalUser.stories.tsx │ │ │ ├── LocalVideoTrack.stories.tsx │ │ │ ├── RemoteAudioTrack.stories.tsx │ │ │ ├── RemoteUser.stories.tsx │ │ │ ├── RemoteVideoTrack.stories.tsx │ │ │ ├── TrackBoundary.stories.tsx │ │ │ └── hooks │ │ │ │ ├── useAutoPlayAudioTrack.en-US.mdx │ │ │ │ ├── useAutoPlayVideoTrack.en-US.mdx │ │ │ │ ├── useClientEvent.en-US.mdx │ │ │ │ ├── useConnectionState.en-US.mdx │ │ │ │ ├── useCurrentUID.en-US.mdx │ │ │ │ ├── useIsConnected.en-US.mdx │ │ │ │ ├── useJoin.en-US.mdx │ │ │ │ ├── useLocalCameraTrack.en-US.mdx │ │ │ │ ├── useLocalMicrophoneTrack.en-US.mdx │ │ │ │ ├── useLocalScreenTrack.en-US.mdx │ │ │ │ ├── useNetworkQuality.en-US.mdx │ │ │ │ ├── usePublish.en-US.mdx │ │ │ │ ├── useRTCClient.en-US.mdx │ │ │ │ ├── useRemoteAudioTracks.en-US.mdx │ │ │ │ ├── useRemoteUserTrack.en-US.mdx │ │ │ │ ├── useRemoteUsers.en-US.mdx │ │ │ │ ├── useRemoteVideoTracks.en-US.mdx │ │ │ │ ├── useTrackEvent.en-US.mdx │ │ │ │ └── useVolumeLevel.en-US.mdx │ │ ├── types.ts │ │ └── vite-env.d.ts │ ├── test │ │ ├── component │ │ │ ├── LocalAudioTrack.test.tsx │ │ │ ├── LocalUser.test.tsx │ │ │ ├── LocalVideoTrack.test.tsx │ │ │ ├── RemoteAudioTrack.test.tsx │ │ │ ├── RemoteUser.test.tsx │ │ │ ├── RemoteVideoTrack.test.tsx │ │ │ └── TrackBoundary.test.tsx │ │ ├── error.test.ts │ │ └── hook │ │ │ ├── useConnectionState.test.ts │ │ │ ├── useCurrentUID.test.ts │ │ │ ├── useIsConnected.test.ts │ │ │ ├── useJoin.test.ts │ │ │ ├── useLocalCameraTrack.test.ts │ │ │ ├── useLocalMicrophoneTrack.test.ts │ │ │ ├── useLocalScreenTrack.test.ts │ │ │ ├── useNetworkQuality.test.ts │ │ │ ├── usePublish.test.ts │ │ │ ├── useRTCClient.test.ts │ │ │ ├── useRemoteAudioTracks.test.ts │ │ │ ├── useRemoteUserTrack.test.tsx │ │ │ ├── useRemoteUsers.test.ts │ │ │ ├── useRemoteVideoTracks.test.ts │ │ │ └── useVolumeLevel.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── typedoc.json │ └── vite.config.ts └── shared │ ├── assets │ └── storybook │ │ └── global.scss │ └── test │ ├── setup.tsx │ └── setup │ ├── agora.tsx │ └── wrapper.tsx ├── patches └── seedrandom@3.0.5.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── scripts ├── const.ts ├── copy-docs.ts ├── docs ├── api.ts ├── component.ts ├── data-types.ts ├── generate-docs.ts ├── generate-storybook-mdx.ts ├── update-readme.ts └── utils.ts ├── publishCN ├── common.sh ├── rewrite-dep.sh └── rewrite-example.sh ├── release ├── clean.ts └── update-version.ts ├── renew.ts ├── tsup └── set-globals.ts ├── upgrade-deps.ts └── utils.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # https://docs.agora.io/en/video-calling/reference/manage-agora-account?platform=web#get-the-app-id 2 | AGORA_APPID= 3 | 4 | # https://docs.agora.io/en/video-calling/develop/integrate-token-generation 5 | # if Security is Disabled in the Agora Console, leave this to blank 6 | # if not you need to `npm run renew` 7 | AGORA_CERTIFICATE= 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | **/dist/* 3 | **/coverage/* 4 | **/node_modules/* 5 | **/storybook-static/* 6 | .snapshots/ 7 | *.min.js 8 | **/typedoc/* 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mdx linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "🐛 Bug report" 2 | description: Create a report to help us improve 3 | assignees: "guoxianzhe" 4 | body: 5 | - type: input 6 | id: version 7 | attributes: 8 | label: agora-rtc-react version 9 | validations: 10 | required: true 11 | - type: input 12 | id: version 13 | attributes: 14 | label: agora-rtc-sdk-ng version 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: steps-to-reproduce 19 | attributes: 20 | label: Steps to reproduce 21 | description: | 22 | What do we need to do to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code. 23 | placeholder: Steps to reproduce 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: expected 28 | attributes: 29 | label: What is your expected? 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: system-info 34 | attributes: 35 | label: System Info 36 | description: Output of `npx envinfo --system --binaries --browsers` 37 | render: shell 38 | placeholder: e.g. System, Binaries, Browsers 39 | - type: textarea 40 | id: additional-comments 41 | attributes: 42 | label: Any additional comments? 43 | description: e.g. some background/context of how you ran into this bug. 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "New feature proposal" 2 | description: Suggest an idea for this project 3 | labels: ["feature request"] 4 | body: 5 | - type: textarea 6 | id: problem-description 7 | attributes: 8 | label: What kind of problem does this feature solve? 9 | description: | 10 | Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature? 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: proposed-API 15 | attributes: 16 | label: What does the proposed API look like? 17 | description: | 18 | Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format your code blocks. 19 | placeholder: Assumed API 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions.yml: -------------------------------------------------------------------------------- 1 | name: "Questions and Help" 2 | description: Looking for help with your app? 3 | labels: ["help wanted"] 4 | body: 5 | - type: textarea 6 | id: problem-description 7 | attributes: 8 | label: What kind of problem do you need help? 9 | description: | 10 | Describe your problem in as much detail as possible so that we can help you better. 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ### What kind of pull request did you make? 14 | 15 | - [ ] New Feature 16 | - [ ] Bug fix 17 | - [ ] Test Case 18 | - [ ] Demo update 19 | - [ ] Other 20 | 21 | ### Related issue link 22 | 23 | 27 | 28 | ### Changelog 29 | 30 | 33 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and pnpm 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Install pnpm 🤏🏻 8 | uses: pnpm/action-setup@v4 9 | with: 10 | version: latest 11 | 12 | - name: Setup Node 💚 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | cache: "pnpm" 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | environment: github-pages 11 | steps: 12 | - name: Checkout 🛎️ 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup 🛠️ 16 | uses: ./.github/actions/setup 17 | 18 | - name: Build 🔧 19 | env: 20 | AGORA_APPID: ${{ secrets.AGORA_APPID }} 21 | AGORA_AES_SALT: ${{secrets.AGORA_AES_SALT}} 22 | run: | 23 | pnpm install 24 | pnpm run lint 25 | pnpm run test 26 | pnpm run renew 27 | pnpm run build 28 | 29 | - name: Build Docs 📖 30 | run: | 31 | pnpm run typedoc 32 | pnpm run build-storybook 33 | pnpm run copy-docs 34 | 35 | - name: Deploy 36 | uses: peaceiris/actions-gh-pages@v3 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./docs 40 | -------------------------------------------------------------------------------- /.github/workflows/gitleak.yml: -------------------------------------------------------------------------------- 1 | name: gitleaks 2 | on: 3 | pull_request: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | scan: 9 | name: gitleaks 10 | runs-on: ubuntu-latest 11 | if: github.actor != 'dependabot[bot]' 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - uses: gitleaks/gitleaks-action@v2 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 19 | GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} 20 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | on: ["pull_request"] 2 | 3 | name: Build and Test PR 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | 10 | permissions: 11 | # Required to checkout the code 12 | contents: read 13 | # Required to put a comment into the pull-request 14 | pull-requests: write 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.ref }} 18 | cancel-in-progress: true 19 | steps: 20 | - name: Checkout 🛎️ 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup 🛠️ 24 | uses: ./.github/actions/setup 25 | 26 | - name: Build 🔧 27 | run: | 28 | pnpm install 29 | pnpm run lint 30 | pnpm run test 31 | pnpm run build 32 | 33 | - name: Report Coverage for agora-rtc-react 🟢 34 | if: always() # Also generate the report if tests are failing 35 | uses: davelosert/vitest-coverage-report-action@v2 36 | with: 37 | working-directory: "./packages/agora-rtc-react" 38 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Sync to shengwang 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | move-to-shengwang: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup 11 | uses: ./.github/actions/setup 12 | 13 | - name: Setup Dependencies 💚 14 | run: | 15 | pnpm install 16 | 17 | - name: Sync to shengwang 18 | uses: AgoraIO-Extensions/actions/.github/actions/shengwang-sync@main 19 | with: 20 | target-repo: "shengwang-rtc-react" 21 | source-repo: "agora-rtc-react" 22 | github-token: ${{ secrets.GH_TOKEN }} 23 | target-branch: ${{ github.ref_name }} 24 | github-email: ${{ secrets.GIT_EMAIL }} 25 | github-private-key: ${{ secrets.GH_PRIVATE_KEY }} 26 | github-username: ${{ secrets.GIT_USERNAME }} 27 | pre-command: | 28 | sh scripts/publishCN/rewrite-dep.sh 29 | sh scripts/publishCN/rewrite-example.sh 30 | pnpm run lint 31 | create-pr: true 32 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: TypeDoc 3 | 4 | on: 5 | # Runs on new releases 6 | release: {} 7 | push: 8 | branches: [main] 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | contents: write 13 | pages: write 14 | id-token: write 15 | 16 | jobs: 17 | # Single deploy job since we're just deploying 18 | deploy: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 🛎️ 22 | uses: actions/checkout@v3 23 | - name: Build TypeDoc 24 | run: | 25 | npm i -g pnpm && pnpm install && pnpm typedoc 26 | - name: Zip It Up 🤐 27 | run: | 28 | zip -r agora-rtc-react-docs.zip typedoc 29 | - name: Upload Artifact ⬆️ 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: agora-rtc-react-docs.zip 33 | path: agora-rtc-react-docs.zip 34 | - name: Upload Doc Archive to GitHub release ⬆️ 35 | if: github.event.release 36 | uses: svenstaro/upload-release-action@2.6.0 37 | with: 38 | file: agora-rtc-react-docs.zip 39 | asset_name: agora-rtc-react-docs.zip 40 | tag: ${{ github.ref_name }} 41 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dep.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade Dependencies 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | package-name: 7 | description: 'upgrade package name: [default: "agora-rtc-sdk-ng"]' 8 | required: true 9 | default: "agora-rtc-sdk-ng" 10 | type: string 11 | package-version: 12 | description: "upgrade package version: [eg: '*.*.*']" 13 | required: true 14 | type: string 15 | 16 | jobs: 17 | upgrade-dep: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Setup 🛠️ 27 | uses: ./.github/actions/setup 28 | 29 | - name: Upgrade Dependencies 🚀 30 | run: | 31 | pnpm install 32 | pnpm -r exec esbuild-dev $PWD/scripts/upgrade-deps.ts dep:${{ inputs.package-name }} @${{ inputs.package-version }} 33 | pnpm -w exec esbuild-dev $PWD/scripts/upgrade-deps.ts dep:${{ inputs.package-name }} @${{ inputs.package-version }} 34 | pnpm install --no-frozen-lockfile 35 | pnpm run test 36 | 37 | - name: Create pull request 38 | uses: AgoraIO-Extensions/actions/.github/actions/pr@main 39 | with: 40 | github-token: ${{ secrets.GH_TOKEN }} 41 | target-repo: ${{ github.workspace }} 42 | target-branch: ${{ github.ref_name }} 43 | target-branch-name-surffix: dep-update 44 | pull-request-title: | 45 | chore: upgrade ${{inputs.package-name}} to ${{inputs.package-version}} 46 | add-paths: . 47 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | [allowlist] 2 | description = "gh pages build file allow lists" 3 | paths = [ 4 | '''gitleaks.toml''', 5 | ] 6 | 7 | # ----- BEGIN Agora AppId ----- 8 | [[rules]] 9 | id = "agora-appid" 10 | description = "Agora AppId" 11 | regex = '''"[0-9a-f]{32}"''' 12 | # ----- END Agora AppId ----- 13 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged --allow-empty 5 | 6 | if command -v gitleaks >/dev/null 2>&1; 7 | then 8 | echo "gitleaks found, proceeding..." 9 | gitleaks protect --staged --source . -v agora-rtc-react.git -c .gitleaks.toml 10 | else 11 | echo "gitleaks not found, skip" 12 | fi 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.16.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/dist/* 2 | **/coverage/* 3 | **/node_modules/* 4 | **/storybook-static/* 5 | pnpm-lock.yaml 6 | **/typedoc/* 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": false, 6 | "endOfLine": "auto", 7 | "arrowParens": "avoid", 8 | "printWidth": 100, 9 | "quoteProps": "consistent" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vivaxy.vscode-conventional-commits", 4 | "EditorConfig.EditorConfig", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "streetsidesoftware.code-spell-checker" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Agora.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "en", 3 | "words": [ 4 | "antd", 5 | "autodocs", 6 | "clsx", 7 | "coverallsapp", 8 | "CRIMX", 9 | "guoxianzhe", 10 | "docgen", 11 | "emsp", 12 | "esbuild", 13 | "falso", 14 | "hyrious", 15 | "iconify", 16 | "iife", 17 | "lcov", 18 | "LichKing", 19 | "lucide", 20 | "ngneat", 21 | "nums", 22 | "paren", 23 | "mobx", 24 | "Parens", 25 | "peaceiris", 26 | "pnpm", 27 | "Preact", 28 | "preinstall", 29 | "stremas", 30 | "treeshake", 31 | "tsup", 32 | "typedoc", 33 | "UNILBS", 34 | "unocss", 35 | "unpublish", 36 | "unpublishes", 37 | "Vals", 38 | "vite", 39 | "vitest", 40 | "yoctocolors", 41 | "zustand" 42 | ], 43 | "ignorePaths": [ 44 | "**/coverage/**", 45 | "**/node_modules/**", 46 | "**/dist/**", 47 | "cspell.config.js", 48 | "pnpm-lock.yaml", 49 | "CHANGELOG.md" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /examples/basic/.env.example: -------------------------------------------------------------------------------- 1 | # https://docs.agora.io/en/video-calling/reference/manage-agora-account?platform=web#get-the-app-id 2 | AGORA_APPID= 3 | 4 | # https://docs.agora.io/en/video-calling/reference/manage-agora-account?platform=web#generate-a-temporary-token 5 | AGORA_CHANNEL=test 6 | 7 | # if Security is Disabled in the Agora Console, leave this to blank 8 | AGORA_TOKEN= 9 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Agora RTC React basic Example 2 | 3 | - Simple example without external state management. 4 | 5 | 6 | 7 | ## Develop 8 | 9 | 1. Add a `.env.local` file to this directory and fill in the Agora account info following the format of `.env.example`. 10 | - You can also add a `.env.local` at monorepo root with `AGORA_APPID` and `AGORA_CERTIFICATE`, then `pnpm renew` to auto-renew tokens. 11 | 2. `pnpm run start`. 12 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Agora RTC React Track Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/basic/index.scss: -------------------------------------------------------------------------------- 1 | .github-corner { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | } 6 | .github-corner:hover .github-corner-svg-arm { 7 | animation: github-corner-svg-wave 560ms ease-in-out; 8 | } 9 | .github-corner-svg { 10 | fill: #151513; 11 | color: #fff; 12 | position: absolute; 13 | top: 0; 14 | border: 0; 15 | right: 0; 16 | &-arm { 17 | transform-origin: 130px 106px; 18 | } 19 | } 20 | @keyframes github-corner-svg-wave { 21 | 0%, 22 | 100% { 23 | transform: rotate(0); 24 | } 25 | 20%, 26 | 60% { 27 | transform: rotate(-25deg); 28 | } 29 | 40%, 30 | 80% { 31 | transform: rotate(10deg); 32 | } 33 | } 34 | @media (max-width: 500px) { 35 | .github-corner:hover .github-corner-svg-arm { 36 | animation: none; 37 | } 38 | .github-corner .github-corner-svg-arm { 39 | animation: github-corner-svg-wave 560ms ease-in-out; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/basic/main.tsx: -------------------------------------------------------------------------------- 1 | import "sanitize.css"; 2 | import "uno.css"; 3 | import AgoraRTC from "agora-rtc-react"; 4 | import { StrictMode } from "react"; 5 | import React from "react"; 6 | import ReactDOM from "react-dom/client"; 7 | import { HashRouter as Router } from "react-router-dom"; 8 | 9 | import App from "./src/App"; 10 | AgoraRTC.setLogLevel(/* DEBUG */ 0); 11 | 12 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 13 | 14 | 15 | 16 | 17 | , 18 | ); 19 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agora-rtc-react-example", 3 | "private": true, 4 | "type": "module", 5 | "main": "./main.tsx", 6 | "scripts": { 7 | "start": "vite --host --open", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "renew": "esbuild-dev ../../scripts/renew.ts --write" 11 | }, 12 | "dependencies": { 13 | "@ant-design/icons": "^5.2.4", 14 | "agora-rtc-react": "workspace:*", 15 | "agora-rtc-react-ui": "workspace:*", 16 | "antd": "^5.6.4", 17 | "clsx": "^1.2.1", 18 | "crypto-js": "^4.2.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-router-dom": "^6.16.0", 22 | "react-select": "^5.7.3", 23 | "sanitize.css": "^13.0.0", 24 | "zustand": "^4.3.9" 25 | }, 26 | "devDependencies": { 27 | "@iconify-json/lucide": "^1.1.110", 28 | "@iconify-json/mdi": "^1.1.53", 29 | "@ngneat/falso": "^6.4.0", 30 | "@types/crypto-js": "^4.1.1", 31 | "sass": "^1.64.0", 32 | "sass-loader": "^13.3.2", 33 | "unocss": "^0.53.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/basic/src/components/AutoLayout.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import type { PropsWithChildren } from "react"; 3 | import { forwardRef } from "react"; 4 | 5 | const Item = /* @__PURE__ */ forwardRef>( 6 | ({ className, children }, ref) => ( 7 |
12 | {children} 13 |
14 | ), 15 | ); 16 | 17 | export const AutoLayout = /* @__PURE__ */ Object.assign( 18 | /* @__PURE__ */ forwardRef>( 19 | ({ className, children }, ref) => ( 20 |
21 | {children} 22 |
23 | ), 24 | ), 25 | { Item }, 26 | ); 27 | -------------------------------------------------------------------------------- /examples/basic/src/components/Client.tsx: -------------------------------------------------------------------------------- 1 | import type { ClientConfig } from "agora-rtc-react"; 2 | import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react"; 3 | import type { ReactNode } from "react"; 4 | import { useState } from "react"; 5 | 6 | interface ClientProps { 7 | children: ReactNode; 8 | clientConfig?: ClientConfig; 9 | } 10 | 11 | export const Client = ({ children, clientConfig = { mode: "rtc", codec: "vp8" } }: ClientProps) => { 12 | const [client] = useState(() => AgoraRTC.createClient(clientConfig)); 13 | return {children}; 14 | }; 15 | 16 | export default Client; 17 | -------------------------------------------------------------------------------- /examples/basic/src/components/Container.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from "react"; 2 | 3 | export const Container = ({ children }: PropsWithChildren) => ( 4 |
{children}
5 | ); 6 | -------------------------------------------------------------------------------- /examples/basic/src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from "react"; 2 | 3 | export const Label = ({ children }: PropsWithChildren) => ( 4 | 5 | {children} 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /examples/basic/src/components/LocalTracks.tsx: -------------------------------------------------------------------------------- 1 | import type { ICameraVideoTrack, IMicrophoneAudioTrack, UID } from "agora-rtc-react"; 2 | import { CameraVideoTrack, MicrophoneAudioTrack } from "agora-rtc-react-ui"; 3 | 4 | export interface LocalTracksProps { 5 | uid: UID; 6 | micOn: boolean; 7 | cameraOn: boolean; 8 | audioTrack?: IMicrophoneAudioTrack | null; 9 | videoTrack?: ICameraVideoTrack | null; 10 | } 11 | 12 | export const LocalTracks = ({ micOn, audioTrack, cameraOn, videoTrack, uid }: LocalTracksProps) => ( 13 |
14 | {micOn && } 15 | {cameraOn && } 16 |
17 | {uid} 18 | {micOn && } 19 |
20 |
21 | ); 22 | -------------------------------------------------------------------------------- /examples/basic/src/components/MediaControl.tsx: -------------------------------------------------------------------------------- 1 | import { SVGCamera, SVGCameraMute, SVGMicrophone, SVGMicrophoneMute } from "agora-rtc-react-ui"; 2 | import clsx from "clsx"; 3 | 4 | interface MediaControlProps { 5 | calling?: boolean; 6 | micOn?: boolean; 7 | cameraOn?: boolean; 8 | setMic?: () => void; 9 | setCamera?: () => void; 10 | setCalling?: () => void; 11 | } 12 | /* Camera and Microphone Controls */ 13 | export const MediaControl = ({ 14 | calling, 15 | micOn, 16 | cameraOn, 17 | setMic, 18 | setCamera, 19 | setCalling, 20 | }: MediaControlProps) => ( 21 | <> 22 |
23 |
24 | {setMic && ( 25 | 28 | )} 29 | {setCamera && ( 30 | 33 | )} 34 |
35 | {setCalling && ( 36 | 42 | )} 43 |
44 | 45 | ); 46 | -------------------------------------------------------------------------------- /examples/basic/src/components/RemoteUsers.tsx: -------------------------------------------------------------------------------- 1 | import type { IRemoteVideoTrack } from "agora-rtc-react"; 2 | import { RemoteVideoPlayer } from "agora-rtc-react-ui"; 3 | 4 | import { fakeAvatar, fakeName } from "../utils"; 5 | 6 | import { AutoLayout } from "./AutoLayout"; 7 | import { Label } from "./Label"; 8 | 9 | export function RenderRemoteUsers({ videoTracks }: { videoTracks: IRemoteVideoTrack[] }) { 10 | return ( 11 | <> 12 | {videoTracks.map((track: IRemoteVideoTrack) => ( 13 | 14 | 15 | 16 | 17 | ))} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/basic/src/components/ToolBox.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Drawer } from "antd"; 2 | import { type ReactNode, useState } from "react"; 3 | 4 | interface ToolBoxProps { 5 | children?: ReactNode; 6 | } 7 | export const ToolBox = ({ children }: ToolBoxProps) => { 8 | const [open, setOpen] = useState(false); 9 | const showDrawer = () => { 10 | setOpen(true); 11 | }; 12 | 13 | const onClose = () => { 14 | setOpen(false); 15 | }; 16 | 17 | const confirm = () => { 18 | setOpen(false); 19 | }; 20 | return ( 21 | <> 22 | {children ? ( 23 | children 24 | ) : ( 25 | <> 26 | 43 | 44 |
45 | 48 |
49 |
50 | 51 | )} 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /examples/basic/src/components/UsersInfo.tsx: -------------------------------------------------------------------------------- 1 | export const UsersInfo = ({ published, total }: { published: number; total: number }) => ( 2 |
3 | 4 | 5 | 6 | {published}/{total} 7 | 8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /examples/basic/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AutoLayout"; 2 | export * from "./Container"; 3 | export * from "./Label"; 4 | export * from "./LocalTracks"; 5 | export * from "./UsersInfo"; 6 | export * from "./Room"; 7 | export * from "./Client"; 8 | export * from "./MediaControl"; 9 | export * from "./ToolBox"; 10 | export * from "./ScreenShare"; 11 | -------------------------------------------------------------------------------- /examples/basic/src/pages/advanced/index.ts: -------------------------------------------------------------------------------- 1 | import MultiChannel from "./multi-channel"; 2 | import ShareScreen from "./share-screen"; 3 | import SwitchLayout from "./switch-layout"; 4 | 5 | const Advanced = [ 6 | { 7 | label: "switch-layout", 8 | component: SwitchLayout, 9 | }, 10 | { 11 | label: "multi-channel", 12 | component: MultiChannel, 13 | }, 14 | { 15 | label: "share-screen", 16 | component: ShareScreen, 17 | }, 18 | ]; 19 | 20 | export { Advanced }; 21 | -------------------------------------------------------------------------------- /examples/basic/src/pages/advanced/multi-channel/index.scss: -------------------------------------------------------------------------------- 1 | .room-selector-item { 2 | width: 100%; 3 | height: 50px; 4 | overflow: hidden; 5 | display: flex; 6 | align-items: center; 7 | flex-wrap: nowrap; 8 | gap: 10px; 9 | padding: 0 10px; 10 | border-bottom-width: 1px; 11 | border-bottom-style: solid; 12 | border-color: #6b72801a; 13 | cursor: pointer; 14 | &:hover { 15 | opacity: 1; 16 | background-color: #6b728005; 17 | } 18 | } 19 | 20 | .user { 21 | position: relative; 22 | width: 54px; 23 | height: 40px; 24 | overflow: hidden; 25 | border-radius: 5px; 26 | } 27 | 28 | .mask { 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100%; 34 | background: linear-gradient( 35 | rgba(0, 0, 0, 0.1) 60%, 36 | rgba(0, 0, 0, 0.3) 80%, 37 | rgba(0, 0, 0, 0.8) 100% 38 | ); 39 | } 40 | 41 | .label { 42 | position: absolute; 43 | z-index: 1; 44 | left: 2px; 45 | bottom: -2px; 46 | width: 100%; 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | word-break: keep-all; 51 | font-size: 12px; 52 | color: #fff; 53 | } 54 | -------------------------------------------------------------------------------- /examples/basic/src/pages/advanced/switch-layout/index.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | display: inline-flex; 3 | flex-direction: column; 4 | background-color: transparent; 5 | border: 1px solid rgba(255, 255, 255, 0.1); 6 | border-radius: 0.25rem; 7 | padding: 0.25rem 0.5rem; 8 | appearance: none; 9 | cursor: pointer; 10 | align-items: center; 11 | font-size: 0; 12 | color: inherit; 13 | &:hover { 14 | background-color: rgba(255, 255, 255, 0.05); 15 | } 16 | &:disabled { 17 | background-color: transparent; 18 | cursor: not-allowed; 19 | } 20 | > i { 21 | font-size: 1.5rem; 22 | } 23 | > span { 24 | font-size: 0.75rem; 25 | } 26 | &-phone { 27 | border-radius: 0.55rem; 28 | padding: 0.5rem 4rem; 29 | color: #fff; 30 | background-color: #16a34a; 31 | &:hover { 32 | background-color: #22c55e; 33 | } 34 | &-active, 35 | &-active:hover { 36 | background-color: #dc2626; 37 | } 38 | } 39 | &-switch-layout { 40 | border: 1px solid; 41 | cursor: pointer; 42 | &-active { 43 | background: red; 44 | } 45 | } 46 | } 47 | .remote { 48 | > .label { 49 | > i { 50 | font-size: 1rem; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/basic/src/pages/basic/index.ts: -------------------------------------------------------------------------------- 1 | import Overview from "./overview"; 2 | 3 | const Basic = [ 4 | { 5 | label: "overview", 6 | component: Overview, 7 | }, 8 | ]; 9 | 10 | export { Basic }; 11 | -------------------------------------------------------------------------------- /examples/basic/src/pages/basic/overview/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | useJoin, 3 | useRemoteAudioTracks, 4 | useRemoteUsers, 5 | useRemoteVideoTracks, 6 | } from "agora-rtc-react"; 7 | import { useState } from "react"; 8 | 9 | import { Container, MediaControl, Room } from "../../../components"; 10 | import { RenderRemoteUsers } from "../../../components/RemoteUsers"; 11 | import { appConfig } from "../../../utils"; 12 | 13 | export const Overview = () => { 14 | const [calling, setCalling] = useState(false); 15 | 16 | useJoin( 17 | { 18 | appid: appConfig.appId, 19 | channel: appConfig.channel, 20 | token: appConfig.token, 21 | }, 22 | calling, 23 | ); 24 | 25 | const [micOn, setMic] = useState(false); 26 | const [cameraOn, setCamera] = useState(false); 27 | 28 | const remoteUsers = useRemoteUsers(); 29 | const { videoTracks } = useRemoteVideoTracks(remoteUsers); 30 | const { audioTracks } = useRemoteAudioTracks(remoteUsers); 31 | audioTracks.map(track => track.play()); 32 | 33 | return ( 34 | 35 | {calling ? ( 36 | } 40 | /> 41 | ) : ( 42 | 43 | )} 44 | { 49 | setCalling(a => !a); 50 | }} 51 | setCamera={() => { 52 | setCamera(a => !a); 53 | }} 54 | setMic={() => { 55 | setMic(a => !a); 56 | }} 57 | /> 58 | 59 | ); 60 | }; 61 | 62 | export default Overview; 63 | -------------------------------------------------------------------------------- /examples/basic/src/pages/component/LocalAudioTrack/index.tsx: -------------------------------------------------------------------------------- 1 | import { LocalAudioTrack, useJoin, useLocalMicrophoneTrack, usePublish } from "agora-rtc-react"; 2 | import { Typography } from "antd"; 3 | import { useEffect, useState } from "react"; 4 | 5 | import { Container, MediaControl } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title, Paragraph } = Typography; 8 | 9 | export const LocalAudioTrackComponent = () => { 10 | useJoin( 11 | { 12 | appid: appConfig.appId, 13 | channel: appConfig.channel, 14 | token: appConfig.token, 15 | }, 16 | true, 17 | ); 18 | 19 | const [micOn, setMic] = useState(false); 20 | const [audioTrackState, setAudioTrackState] = useState<{ 21 | muted: boolean; 22 | isPlaying: boolean; 23 | enabled: boolean; 24 | }>(); 25 | const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn); 26 | 27 | usePublish([localMicrophoneTrack]); 28 | useEffect(() => { 29 | if (localMicrophoneTrack) { 30 | setAudioTrackState({ 31 | muted: localMicrophoneTrack.muted, 32 | isPlaying: localMicrophoneTrack.isPlaying, 33 | enabled: localMicrophoneTrack.enabled, 34 | }); 35 | } 36 | }, [micOn, localMicrophoneTrack]); 37 | return ( 38 | 39 |
40 | local audio track status 41 | {audioTrackState && ( 42 | <> 43 | {`muted: ${audioTrackState?.muted}`} 44 | {`isPlaying: ${audioTrackState.isPlaying}`} 45 | {`enabled: ${audioTrackState?.enabled}`} 46 | 47 | )} 48 | 49 |
50 | { 53 | setMic(a => !a); 54 | }} 55 | /> 56 |
57 | ); 58 | }; 59 | 60 | export default LocalAudioTrackComponent; 61 | -------------------------------------------------------------------------------- /examples/basic/src/pages/component/LocalVideoTrack/index.tsx: -------------------------------------------------------------------------------- 1 | import { LocalVideoTrack, useJoin, useLocalCameraTrack, usePublish } from "agora-rtc-react"; 2 | import { Typography } from "antd"; 3 | import { useState } from "react"; 4 | 5 | import { Container, MediaControl } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title } = Typography; 8 | 9 | export const LocalVideoTrackComponent = () => { 10 | useJoin( 11 | { 12 | appid: appConfig.appId, 13 | channel: appConfig.channel, 14 | token: appConfig.token, 15 | }, 16 | true, 17 | ); 18 | 19 | const [cameraOn, setCamera] = useState(false); 20 | const { localCameraTrack } = useLocalCameraTrack(); 21 | usePublish([localCameraTrack]); 22 | return ( 23 | 24 |
25 | local video track 26 | 33 |
34 | { 37 | setCamera(a => !a); 38 | }} 39 | /> 40 |
41 | ); 42 | }; 43 | 44 | export default LocalVideoTrackComponent; 45 | -------------------------------------------------------------------------------- /examples/basic/src/pages/component/index.ts: -------------------------------------------------------------------------------- 1 | import LocalAudioTrackComponent from "./LocalAudioTrack"; 2 | import LocalUserComponent from "./LocalUser"; 3 | import LocalVideoTrackComponent from "./LocalVideoTrack"; 4 | import RemoteAudioTrackComponent from "./RemoteAudioTrack"; 5 | import RemoteUserComponent from "./RemoteUser"; 6 | import RemoteVideoTrackComponent from "./RemoteVideoTrack"; 7 | 8 | const Components = [ 9 | { 10 | label: "LocalAudioTrack", 11 | component: LocalAudioTrackComponent, 12 | }, 13 | { 14 | label: "RemoteAudioTrack", 15 | component: RemoteAudioTrackComponent, 16 | }, 17 | { 18 | label: "LocalVideoTrack", 19 | component: LocalVideoTrackComponent, 20 | }, 21 | { 22 | label: "RemoteVideoTrack", 23 | component: RemoteVideoTrackComponent, 24 | }, 25 | { 26 | label: "LocalUser", 27 | component: LocalUserComponent, 28 | }, 29 | { 30 | label: "RemoteUser", 31 | component: RemoteUserComponent, 32 | }, 33 | ]; 34 | 35 | export { Components }; 36 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/index.ts: -------------------------------------------------------------------------------- 1 | import UseAutoPlayAudioTrack from "./useAutoPlayAudioTrack"; 2 | import UseAutoPlayVideoTrack from "./useAutoPlayVideoTrack"; 3 | import UseClientEvent from "./useClientEvent"; 4 | import UseConnectionState from "./useConnectionState"; 5 | import UseCurrentUID from "./useCurrentUID"; 6 | import UseIsConnected from "./useIsConnected"; 7 | import UseJoin from "./useJoin"; 8 | import UseLocalScreenTrack from "./useLocalScreenTrack"; 9 | import UseNetworkQuality from "./useNetworkQuality"; 10 | import UsePublish from "./usePublish"; 11 | import UseRemoteUsers from "./useRemoteUsers"; 12 | const Hooks = [ 13 | { 14 | label: "useJoin", 15 | component: UseJoin, 16 | }, 17 | { 18 | label: "usePublish", 19 | component: UsePublish, 20 | }, 21 | { 22 | label: "useRemoteUsers", 23 | component: UseRemoteUsers, 24 | }, 25 | { 26 | label: "useConnectionState", 27 | component: UseConnectionState, 28 | }, 29 | { 30 | label: "useIsConnected", 31 | component: UseIsConnected, 32 | }, 33 | { 34 | label: "useCurrentUID", 35 | component: UseCurrentUID, 36 | }, 37 | { 38 | label: "useAutoPlayAudioTrack", 39 | component: UseAutoPlayAudioTrack, 40 | }, 41 | { 42 | label: "useAutoPlayVideoTrack", 43 | component: UseAutoPlayVideoTrack, 44 | }, 45 | { 46 | label: "useNetworkQuality", 47 | component: UseNetworkQuality, 48 | }, 49 | { 50 | label: "useLocalScreenTrack", 51 | component: UseLocalScreenTrack, 52 | }, 53 | { 54 | label: "UseClientEvent", 55 | component: UseClientEvent, 56 | }, 57 | ]; 58 | 59 | export { Hooks }; 60 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useAutoPlayAudioTrack/index.tsx: -------------------------------------------------------------------------------- 1 | import { useAutoPlayAudioTrack, useJoin, useLocalMicrophoneTrack } from "agora-rtc-react"; 2 | import { Typography } from "antd"; 3 | import { useState } from "react"; 4 | 5 | import { Container, MediaControl } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title } = Typography; 8 | 9 | export const UseAutoPlayAudioTrack = () => { 10 | useJoin( 11 | { 12 | appid: appConfig.appId, 13 | channel: appConfig.channel, 14 | token: appConfig.token, 15 | }, 16 | true, 17 | ); 18 | 19 | const [micOn, setMic] = useState(false); 20 | const { localMicrophoneTrack } = useLocalMicrophoneTrack(); 21 | 22 | useAutoPlayAudioTrack(localMicrophoneTrack, micOn); 23 | 24 | return ( 25 | 26 |
27 | useAutoPlayAudioTrack 28 |
29 | { 32 | setMic(a => !a); 33 | }} 34 | /> 35 |
36 | ); 37 | }; 38 | 39 | export default UseAutoPlayAudioTrack; 40 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useAutoPlayVideoTrack/index.tsx: -------------------------------------------------------------------------------- 1 | import { useAutoPlayVideoTrack, useJoin, useLocalCameraTrack } from "agora-rtc-react"; 2 | import { Typography } from "antd"; 3 | import { useRef, useState } from "react"; 4 | 5 | import { Container, MediaControl } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title } = Typography; 8 | 9 | export const UseAutoPlayVideoTrack = () => { 10 | useJoin( 11 | { 12 | appid: appConfig.appId, 13 | channel: appConfig.channel, 14 | token: appConfig.token, 15 | }, 16 | true, 17 | ); 18 | 19 | const [cameraOn, setCamera] = useState(false); 20 | 21 | const div = useRef(null); 22 | const { localCameraTrack } = useLocalCameraTrack(); 23 | useAutoPlayVideoTrack(localCameraTrack, cameraOn, div.current); 24 | 25 | return ( 26 | 27 |
28 | useAutoPlayVideoTrack 29 |
30 |
(div.current = ref)} style={{ width: "150px", height: "100px" }} /> 31 |
32 |
33 | { 36 | setCamera(a => !a); 37 | }} 38 | /> 39 | 40 | ); 41 | }; 42 | 43 | export default UseAutoPlayVideoTrack; 44 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useConnectionState/index.tsx: -------------------------------------------------------------------------------- 1 | import { useConnectionState, useJoin } from "agora-rtc-react"; 2 | import { Button, Typography } from "antd"; 3 | import { useState } from "react"; 4 | 5 | import { Container } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title, Paragraph, Text } = Typography; 8 | 9 | export const UseConnectionState = () => { 10 | const [calling, setCalling] = useState(false); 11 | const connectionState = useConnectionState(); 12 | useJoin( 13 | { 14 | appid: appConfig.appId, 15 | channel: appConfig.channel, 16 | token: appConfig.token, 17 | }, 18 | calling, 19 | ); 20 | 21 | return ( 22 | 23 |
24 | useConnectionState 25 | 26 | By using useConnectionState hook to get the current connection state 27 | of the client. 28 | 29 | {`result: ${connectionState}`} 30 | 31 | 34 | 37 | 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default UseConnectionState; 44 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useCurrentUID/index.tsx: -------------------------------------------------------------------------------- 1 | import { useCurrentUID, useJoin } from "agora-rtc-react"; 2 | import { Button, Typography } from "antd"; 3 | import { useState } from "react"; 4 | 5 | import { Container } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title, Paragraph, Text } = Typography; 8 | 9 | export const UseCurrentUID = () => { 10 | const [calling, setCalling] = useState(false); 11 | const uid = useCurrentUID(); 12 | useJoin( 13 | { 14 | appid: appConfig.appId, 15 | channel: appConfig.channel, 16 | token: appConfig.token, 17 | }, 18 | calling, 19 | ); 20 | 21 | return ( 22 | 23 |
24 | useCurrentUID 25 | 26 | By using useCurrentUID hook to get the UID of the current user. 27 | 28 | {`result: ${uid}`} 29 | 30 | 33 | 36 | 37 |
38 |
39 | ); 40 | }; 41 | 42 | export default UseCurrentUID; 43 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useIsConnected/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIsConnected, useJoin } from "agora-rtc-react"; 2 | import { Button, Typography } from "antd"; 3 | import { useState } from "react"; 4 | 5 | import { Container } from "../../../components"; 6 | import { appConfig } from "../../../utils"; 7 | const { Title, Paragraph, Text } = Typography; 8 | 9 | export const UseIsConnected = () => { 10 | const [calling, setCalling] = useState(false); 11 | const isConnected = useIsConnected(); 12 | useJoin( 13 | { 14 | appid: appConfig.appId, 15 | channel: appConfig.channel, 16 | token: appConfig.token, 17 | }, 18 | calling, 19 | ); 20 | 21 | return ( 22 | 23 |
24 | useIsConnected 25 | 26 | By using useIsConnected hook to determines whether the client is 27 | successfully connected to Agora server. 28 | 29 | {`result: ${isConnected}`} 30 | 31 | 34 | 37 | 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default UseIsConnected; 44 | -------------------------------------------------------------------------------- /examples/basic/src/pages/hook/useNetworkQuality/index.tsx: -------------------------------------------------------------------------------- 1 | import { useJoin, useNetworkQuality } from "agora-rtc-react"; 2 | import { Typography } from "antd"; 3 | 4 | import { Container } from "../../../components"; 5 | import { appConfig } from "../../../utils"; 6 | const { Title, Paragraph } = Typography; 7 | 8 | export const UseNetworkQuality = () => { 9 | const networkQuality = useNetworkQuality(); 10 | useJoin( 11 | { 12 | appid: appConfig.appId, 13 | channel: appConfig.channel, 14 | token: appConfig.token, 15 | }, 16 | true, 17 | ); 18 | 19 | return ( 20 | 21 |
22 | useNetworkQuality 23 | {`result: ${JSON.stringify(networkQuality)}`} 24 |
25 |
26 | ); 27 | }; 28 | 29 | export default UseNetworkQuality; 30 | -------------------------------------------------------------------------------- /examples/basic/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | import MultiChannel from "./advanced/multi-channel"; 2 | import SwitchLayout from "./advanced/switch-layout"; 3 | import Overview from "./basic/overview"; 4 | import UseJoin from "./hook/useJoin"; 5 | import UsePublish from "./hook/usePublish"; 6 | 7 | const Pages = [ 8 | { 9 | label: "switch-layout", 10 | component: SwitchLayout, 11 | }, 12 | { 13 | label: "overview", 14 | component: Overview, 15 | }, 16 | { 17 | label: "multi-channel", 18 | component: MultiChannel, 19 | }, 20 | { 21 | label: "useJoin", 22 | component: UseJoin, 23 | }, 24 | { 25 | label: "usePublish", 26 | component: UsePublish, 27 | }, 28 | ]; 29 | 30 | export { Pages }; 31 | -------------------------------------------------------------------------------- /examples/basic/src/pages/setting/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Form, Input, notification } from "antd"; 2 | import { useState } from "react"; 3 | 4 | import { Container } from "../../components"; 5 | import { appConfig } from "../../utils"; 6 | 7 | type NotificationType = "success" | "info" | "warning" | "error"; 8 | 9 | export const Setting = () => { 10 | const [channel, setChannel] = useState(appConfig.channel); 11 | 12 | const handleChannelNameChange = (e: React.ChangeEvent) => { 13 | setChannel(e.target.value); 14 | }; 15 | const [api, contextHolder] = notification.useNotification(); 16 | 17 | const openNotificationWithIcon = (type: NotificationType) => { 18 | api[type]({ 19 | message: "Configuration Saved", 20 | description: "Go to other pages and try it now!", 21 | }); 22 | }; 23 | 24 | const confirm = () => { 25 | appConfig.channel = channel; 26 | openNotificationWithIcon("success"); 27 | }; 28 | 29 | const buttonItemLayout = { wrapperCol: { span: 14, offset: 4 } }; 30 | return ( 31 | 32 | {contextHolder} 33 |
34 | 35 | 36 | 37 |
38 | 39 | 42 | 43 |
44 | ); 45 | }; 46 | 47 | export default Setting; 48 | -------------------------------------------------------------------------------- /examples/basic/src/utils/fake.ts: -------------------------------------------------------------------------------- 1 | import { randFirstName, seed } from "@ngneat/falso"; 2 | import type { UID } from "agora-rtc-react"; 3 | 4 | import { appConfig } from "./const"; 5 | 6 | export const fakeName = (uid: UID): string => { 7 | seed(String(uid)); 8 | const name = randFirstName(); 9 | seed(); 10 | return name; 11 | }; 12 | 13 | export const fakeAvatar = (): string => { 14 | return "https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg"; 15 | }; 16 | 17 | export const fakeFetch = (url: string): Promise => { 18 | return new Promise(resolve => { 19 | let responseText = ""; 20 | if (url === "/get-token") { 21 | responseText = JSON.stringify({ 22 | appid: appConfig.appId, 23 | channel: appConfig.channel, 24 | token: appConfig.token, 25 | }); 26 | } else { 27 | responseText = "404 Not Found."; 28 | } 29 | resolve(responseText); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /examples/basic/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fake"; 2 | export * from "./const"; 3 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src", "src/App.tsx", "vite-env.d.ts"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/basic/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly AGORA_APPID: string; 5 | readonly AGORA_CHANNEL: string; 6 | readonly AGORA_TOKEN: string; 7 | readonly AGORA_AES_SALT: string; 8 | } 9 | -------------------------------------------------------------------------------- /examples/basic/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import presetIcons from "unocss/preset-icons"; 3 | import presetUno from "unocss/preset-uno"; 4 | import uno from "unocss/vite"; 5 | import { defineConfig } from "vite"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | base: "./", 10 | plugins: [ 11 | // https://uno.antfu.me/ 12 | uno({ 13 | presets: [presetUno(), presetIcons()], 14 | }), 15 | react(), 16 | ], 17 | envPrefix: "AGORA_", 18 | }); 19 | -------------------------------------------------------------------------------- /examples/mobx/.env.example: -------------------------------------------------------------------------------- 1 | # https://docs.agora.io/en/video-calling/reference/manage-agora-account?platform=web#get-the-app-id 2 | AGORA_APPID= 3 | 4 | # https://docs.agora.io/en/video-calling/reference/manage-agora-account?platform=web#generate-a-temporary-token 5 | AGORA_CHANNEL=test 6 | 7 | # if Security is Disabled in the Agora Console, leave this to blank 8 | AGORA_TOKEN= 9 | -------------------------------------------------------------------------------- /examples/mobx/README.md: -------------------------------------------------------------------------------- 1 | # Agora RTC React MobX Example 2 | 3 | - MobX for state management. 4 | - Custom user name and avatar. 5 | - Create tracks on demand. 6 | - import React & SDK by CDN. 7 | 8 | 9 | 10 | ## Develop 11 | 12 | 1. Add a `.env.local` file to this directory and fill in the Agora account info following the format of `.env.example`. 13 | - You can also add a `.env.local` at monorepo root with `AGORA_APPID` and `AGORA_CERTIFICATE`, then `pnpm renew` to auto-renew tokens. 14 | 2. `pnpm run start`. 15 | -------------------------------------------------------------------------------- /examples/mobx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Agora RTC React MobX Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/mobx/index.scss: -------------------------------------------------------------------------------- 1 | .github-corner { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | } 6 | .github-corner:hover .github-corner-svg-arm { 7 | animation: github-corner-svg-wave 560ms ease-in-out; 8 | } 9 | .github-corner-svg { 10 | fill: #151513; 11 | color: #fff; 12 | position: absolute; 13 | top: 0; 14 | border: 0; 15 | right: 0; 16 | &-arm { 17 | transform-origin: 130px 106px; 18 | } 19 | } 20 | @keyframes github-corner-svg-wave { 21 | 0%, 22 | 100% { 23 | transform: rotate(0); 24 | } 25 | 20%, 26 | 60% { 27 | transform: rotate(-25deg); 28 | } 29 | 40%, 30 | 80% { 31 | transform: rotate(10deg); 32 | } 33 | } 34 | @media (max-width: 500px) { 35 | .github-corner:hover .github-corner-svg-arm { 36 | animation: none; 37 | } 38 | .github-corner .github-corner-svg-arm { 39 | animation: github-corner-svg-wave 560ms ease-in-out; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/mobx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-mobx", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "vite --host --open", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "renew": "esbuild-dev ../../scripts/renew.ts --write" 10 | }, 11 | "dependencies": { 12 | "@ngneat/falso": "^6.4.0", 13 | "agora-rtc-react": "workspace:*", 14 | "agora-rtc-react-ui": "workspace:*", 15 | "clsx": "^1.2.1", 16 | "crypto-js": "^4.2.0", 17 | "mobx": "^6.10.0", 18 | "mobx-react-lite": "^4.0.3", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "sanitize.css": "^13.0.0", 22 | "side-effect-manager": "^1.2.2" 23 | }, 24 | "devDependencies": { 25 | "@iconify-json/mdi": "^1.1.53", 26 | "@types/crypto-js": "^4.1.1", 27 | "unocss": "^0.53.5", 28 | "vite-plugin-externals": "^0.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/mobx/src/components/Controls.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { observer } from "mobx-react-lite"; 3 | 4 | import { appStore } from "../stores/app.store"; 5 | import type { MyLocalUser } from "../stores/local-user.store"; 6 | 7 | interface ControlsProps { 8 | localUser: MyLocalUser; 9 | } 10 | 11 | export const Controls = observer(function Controls({ localUser }: ControlsProps) { 12 | const { shareScreen } = appStore; 13 | 14 | return ( 15 |
16 | 27 | 34 |
35 | 43 |
44 | 48 |
49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /examples/mobx/src/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react-lite"; 2 | import { useState } from "react"; 3 | 4 | import { appId, channel, token } from "../constants"; 5 | import { appStore } from "../stores/app.store"; 6 | import { useSafePromise } from "../utils"; 7 | 8 | import { RoomInfo } from "./RoomInfo"; 9 | import { JoinButton, NewButton, ScheduleButton, ScreenShareButton } from "./buttons"; 10 | 11 | export const Home = observer(function Home() { 12 | const sp = useSafePromise(); 13 | const [isLoading, setLoading] = useState(false); 14 | const joinChannel = () => { 15 | setLoading(true); 16 | sp(appStore.join(appId, channel, token)).then(() => setLoading(false)); 17 | }; 18 | 19 | return ( 20 | 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/mobx/src/components/LocalUser.tsx: -------------------------------------------------------------------------------- 1 | import { LocalMicrophoneAndCameraUser, MicControl } from "agora-rtc-react-ui"; 2 | import { observer } from "mobx-react-lite"; 3 | 4 | import type { MyLocalUser } from "../stores/local-user.store"; 5 | 6 | interface LocalUserProps { 7 | className?: string; 8 | localUser: MyLocalUser; 9 | } 10 | 11 | export const LocalUser = observer(function LocalUser({ className, localUser }: LocalUserProps) { 12 | return ( 13 |
14 | 22 | {localUser.name} 23 | {localUser.micOn && } 24 | 25 |
26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/mobx/src/components/Room.tsx: -------------------------------------------------------------------------------- 1 | import { RemoteUser } from "agora-rtc-react"; 2 | import { MicControl } from "agora-rtc-react-ui"; 3 | import { observer } from "mobx-react-lite"; 4 | 5 | import { appStore } from "../stores/app.store"; 6 | 7 | import { Controls } from "./Controls"; 8 | import { LocalUser } from "./LocalUser"; 9 | import { ShareScreenTracks } from "./ShareScreenTracks"; 10 | import { Users } from "./Users"; 11 | 12 | export const Room = observer(function Room() { 13 | const { localUser, remoteUsers } = appStore.users; 14 | 15 | return ( 16 |
17 |
18 |
19 |
20 | {localUser && } 21 | {remoteUsers.map(user => ( 22 |
23 | 29 | {user.name} 30 | {user.micOn && } 31 | 32 |
33 | ))} 34 | 35 |
36 | {localUser && } 37 |
38 | 39 |
40 |
41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /examples/mobx/src/components/RoomInfo.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function RoomInfo({ onJoin }: { onJoin?: () => void }) { 4 | const [now, setNow] = useState(Date.now()); 5 | 6 | useEffect(() => { 7 | const ticket = setInterval(() => setNow(Date.now()), 1000); 8 | return () => clearInterval(ticket); 9 | }, []); 10 | 11 | const datetime = new Date(now); 12 | const time = 13 | datetime && 14 | datetime.toLocaleTimeString("en-US", { 15 | hour: "2-digit", 16 | minute: "2-digit", 17 | hour12: false, 18 | }); 19 | const date = 20 | datetime && 21 | datetime.toLocaleDateString("en-US", { 22 | year: "numeric", 23 | month: "numeric", 24 | day: "numeric", 25 | weekday: "long", 26 | }); 27 | 28 | return ( 29 |
30 |
31 |
{time}
32 |
{date}
33 |
34 |
35 |
36 |

Example Meeting

37 |

11:00~16:00

38 |
39 | 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /examples/mobx/src/components/ShareScreenTracks.tsx: -------------------------------------------------------------------------------- 1 | import { RemoteVideoTrack } from "agora-rtc-react"; 2 | import { observer } from "mobx-react-lite"; 3 | 4 | import { appStore } from "../stores/app.store"; 5 | 6 | export const ShareScreenTracks = observer(function ShareScreenTracks() { 7 | const { shareScreen } = appStore; 8 | return ; 9 | }); 10 | -------------------------------------------------------------------------------- /examples/mobx/src/components/Users.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react-lite"; 2 | 3 | import { appStore } from "../stores/app.store"; 4 | 5 | export const Users = observer(function Users() { 6 | const { localUser, remoteUsers } = appStore.users; 7 | 8 | return ( 9 |
10 |

Users ({remoteUsers.length + 1})

11 |
12 |
13 | 14 | {localUser?.name} (You) 15 | {localUser?.micOn && } 16 | {localUser?.cameraOn && } 17 |
18 | {remoteUsers.map(user => ( 19 |
20 | 21 | {user.name} 22 | {user.micOn && } 23 | {user.cameraOn && } 24 |
25 | ))} 26 |
27 |
28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/mobx/src/components/buttons.tsx: -------------------------------------------------------------------------------- 1 | export function JoinButton({ onClick, isLoading }: { onClick?: () => void; isLoading?: boolean }) { 2 | return ( 3 | 9 | ); 10 | } 11 | 12 | export function NewButton() { 13 | return ( 14 | 20 | ); 21 | } 22 | 23 | export function ScheduleButton() { 24 | return ( 25 | 31 | ); 32 | } 33 | 34 | export function ScreenShareButton() { 35 | return ( 36 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /examples/mobx/src/constants.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from "crypto-js"; 2 | 3 | let id = import.meta.env.AGORA_APPID; 4 | if (import.meta.env.AGORA_AES_SALT) { 5 | // only for github-pages demo 6 | const bytes = CryptoJS.AES.decrypt(import.meta.env.AGORA_APPID, import.meta.env.AGORA_AES_SALT); 7 | id = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); 8 | } 9 | export const appId = id; 10 | export const channel = import.meta.env.AGORA_CHANNEL || "test"; 11 | export const token = import.meta.env.AGORA_TOKEN ? import.meta.env.AGORA_TOKEN : null; 12 | -------------------------------------------------------------------------------- /examples/mobx/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { AppStore } from "./stores/app.store"; 2 | declare global { 3 | interface Window { 4 | appStore: AppStore; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/mobx/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "uno.css"; 2 | import "./styles.css"; 3 | 4 | import { StrictMode } from "react"; 5 | import { createRoot } from "react-dom/client"; 6 | 7 | import { App } from "./components/App"; 8 | 9 | createRoot(document.getElementById("root") as HTMLElement).render( 10 | 11 | 12 | , 13 | ); 14 | -------------------------------------------------------------------------------- /examples/mobx/src/stores/app.store.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-react"; 2 | import AgoraRTC from "agora-rtc-react"; 3 | import { makeAutoObservable } from "mobx"; 4 | 5 | import { ShareScreen } from "./share-screen.store"; 6 | import { Users } from "./users.store"; 7 | 8 | AgoraRTC.setLogLevel(/* warning */ 2); 9 | 10 | class AppStore { 11 | client: IAgoraRTCClient | null = null; 12 | users = new Users(); 13 | shareScreen = new ShareScreen(); 14 | 15 | get uid() { 16 | return this.client?.uid; 17 | } 18 | 19 | constructor() { 20 | this.users.localUIDs.push(this.shareScreen.uid); 21 | makeAutoObservable(this); 22 | } 23 | 24 | async join(appid: string, channel: string, token: string | null): Promise { 25 | const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); 26 | await client.join(appid, channel, token, null); 27 | this._updateClient(client); 28 | } 29 | 30 | async leave(): Promise { 31 | this.users.dispose(); 32 | await this.shareScreen.dispose(); 33 | const client = this.client; 34 | if (client) { 35 | await client.leave(); 36 | this._updateClient(null); 37 | } 38 | } 39 | 40 | private _updateClient(client: IAgoraRTCClient | null): void { 41 | this.client = client; 42 | this.users.updateClient(client); 43 | this.shareScreen.updateMainClient(client); 44 | } 45 | } 46 | 47 | export type { AppStore }; 48 | 49 | export const appStore = new AppStore(); 50 | 51 | if (import.meta.env.DEV) { 52 | window.appStore = appStore; 53 | } 54 | -------------------------------------------------------------------------------- /examples/mobx/src/stores/remote-user.store.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | IAgoraRTCRemoteUser, 3 | IRemoteAudioTrack, 4 | IRemoteVideoTrack, 5 | UID, 6 | } from "agora-rtc-react"; 7 | import { makeAutoObservable } from "mobx"; 8 | 9 | import { fakeAvatar, fakeName } from "../utils"; 10 | 11 | /** 12 | * This class extracts fields from the IAgoraRTCRemoteUser object so that mobx can track them. 13 | * 14 | * `uid`, `audioTrack`, `videoTrack` → same\ 15 | * `hasAudio` → `micOn`\ 16 | * `hasVideo` → `cameraOn` 17 | */ 18 | export class MyRemoteUser { 19 | uid: UID; 20 | name: string; 21 | avatar: string; 22 | rtcUser: IAgoraRTCRemoteUser; 23 | cameraOn: boolean; 24 | micOn: boolean; 25 | videoTrack?: IRemoteVideoTrack; 26 | audioTrack?: IRemoteAudioTrack; 27 | 28 | constructor(rtcUser: IAgoraRTCRemoteUser) { 29 | this.uid = rtcUser.uid; 30 | this.name = fakeName(rtcUser.uid); 31 | this.avatar = fakeAvatar(); 32 | this.rtcUser = rtcUser; 33 | this.micOn = rtcUser.hasAudio; 34 | this.cameraOn = rtcUser.hasVideo; 35 | this.audioTrack = rtcUser.audioTrack; 36 | this.videoTrack = rtcUser.videoTrack; 37 | 38 | makeAutoObservable(this); 39 | } 40 | 41 | update(rtcUser: IAgoraRTCRemoteUser) { 42 | this.rtcUser = rtcUser; 43 | this.micOn = rtcUser.hasAudio; 44 | this.cameraOn = rtcUser.hasVideo; 45 | this.audioTrack = rtcUser.audioTrack; 46 | this.videoTrack = rtcUser.videoTrack; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/mobx/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { randFirstName, seed } from "@ngneat/falso"; 2 | import type { UID } from "agora-rtc-react"; 3 | import type { RefObject } from "react"; 4 | import { useCallback, useEffect, useRef } from "react"; 5 | 6 | export const fakeName = (uid: UID): string => { 7 | seed(String(uid)); 8 | const name = randFirstName(); 9 | seed(); 10 | return name; 11 | }; 12 | 13 | export const fakeAvatar = (): string => { 14 | return "https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg"; 15 | }; 16 | 17 | export function useIsUnmounted(): RefObject { 18 | const isUnmountRef = useRef(false); 19 | useEffect(() => { 20 | isUnmountRef.current = false; 21 | return () => { 22 | isUnmountRef.current = true; 23 | }; 24 | }, []); 25 | return isUnmountRef; 26 | } 27 | 28 | export function useSafePromise() { 29 | const isUnmountRef = useIsUnmounted(); 30 | 31 | function safePromise( 32 | promise: PromiseLike, 33 | onUnmountedError?: (error: E) => void, 34 | ) { 35 | // the async promise executor is intended 36 | // eslint-disable-next-line no-async-promise-executor 37 | return new Promise(async (resolve, reject) => { 38 | try { 39 | const result = await promise; 40 | if (!isUnmountRef.current) { 41 | resolve(result); 42 | } 43 | // unresolved promises will be garbage collected. 44 | } catch (error) { 45 | if (!isUnmountRef.current) { 46 | reject(error); 47 | } else if (onUnmountedError) { 48 | onUnmountedError(error as E); 49 | } else { 50 | if (process.env.NODE_ENV === "development") { 51 | console.error("An error occurs from a promise after a component is unmounted", error); 52 | } 53 | } 54 | } 55 | }); 56 | } 57 | 58 | return useCallback(safePromise, [isUnmountRef]); 59 | } 60 | -------------------------------------------------------------------------------- /examples/mobx/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly AGORA_APPID: string; 5 | readonly AGORA_CHANNEL: string; 6 | readonly AGORA_TOKEN: string; 7 | readonly AGORA_AES_SALT: string; 8 | } 9 | -------------------------------------------------------------------------------- /examples/mobx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/mobx/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/mobx/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import presetIcons from "unocss/preset-icons"; 3 | import uno from "unocss/vite"; 4 | import { defineConfig } from "vite"; 5 | import { viteExternalsPlugin } from "vite-plugin-externals"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | base: "./", 10 | plugins: [ 11 | react(), 12 | uno({ presets: [presetIcons()] }), 13 | viteExternalsPlugin({ 14 | "react": "React", 15 | "react-dom": "ReactDOM", 16 | "react-dom/client": "ReactDOM", 17 | "agora-rtc-react": "AgoraRTC", 18 | }), 19 | ], 20 | envPrefix: "AGORA_", 21 | }); 22 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/.storybook/main.js: -------------------------------------------------------------------------------- 1 | const { mergeConfig } = require("vite"); 2 | 3 | module.exports = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-links", 7 | "@storybook/addon-essentials", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/react-vite", 12 | options: {}, 13 | }, 14 | docs: { 15 | autodocs: "tag", 16 | }, 17 | core: { 18 | disableTelemetry: true, 19 | }, 20 | typescript: { 21 | reactDocgen: "react-docgen", 22 | skipBabel: true, 23 | check: false, 24 | }, 25 | async viteFinal(config) { 26 | return mergeConfig(config, { 27 | resolve: { 28 | alias: { 29 | "agora-rtc-sdk-ng": require.resolve("agora-rtc-sdk-ng-fake/src/index.ts"), 30 | }, 31 | }, 32 | }); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import "../../shared/assets/storybook/global.scss"; 2 | 3 | export const parameters = { 4 | backgrounds: { 5 | default: "white", 6 | values: [ 7 | { 8 | name: "white", 9 | value: "#fff", 10 | }, 11 | { 12 | name: "light", 13 | value: "rgb(248, 248, 248)", 14 | }, 15 | { 16 | name: "grey", 17 | value: "#555", 18 | }, 19 | { 20 | name: "dark", 21 | value: "rgb(25, 27, 31)", 22 | }, 23 | ], 24 | }, 25 | actions: { argTypesRegex: "^on[A-Z].*" }, 26 | controls: { 27 | expanded: true, 28 | matchers: { 29 | color: /(background|color)$/i, 30 | date: /Date$/, 31 | }, 32 | }, 33 | options: { 34 | showPanel: true, 35 | }, 36 | docs: { 37 | canvas: { 38 | sourceState: "shown", 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agora-rtc-react-ui", 3 | "version": "0.0.1", 4 | "private": false, 5 | "description": "Agora RTC React SDK UI", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/AgoraIO-Extensions/agora-rtc-react.git", 10 | "directory": "packages/agora-rtc-react-ui" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "agora-rtc-react-ui" 15 | ], 16 | "bugs": { 17 | "url": "https://github.com/AgoraIO-Extensions/agora-rtc-react/issues" 18 | }, 19 | "contributors": [ 20 | "hyrious", 21 | "LichKing-2234", 22 | "CRIMX", 23 | "guoxianzhe" 24 | ], 25 | "main": "src/index.ts", 26 | "publishConfig": { 27 | "main": "dist/agora-rtc-react-ui.js", 28 | "module": "dist/agora-rtc-react-ui.mjs", 29 | "types": "dist/agora-rtc-react-ui.d.ts" 30 | }, 31 | "source": "src/index.ts", 32 | "files": [ 33 | "dist" 34 | ], 35 | "engines": { 36 | "node": ">=10" 37 | }, 38 | "scripts": { 39 | "prepublishOnly": "pnpm run build", 40 | "build": "tsup", 41 | "release": "release-it", 42 | "start": "pnpm run -F example-overview start", 43 | "storybook": "storybook dev -p 6006", 44 | "build-storybook": "storybook build", 45 | "storybook-docs": "storybook dev --docs", 46 | "build-storybook-docs": "storybook build --docs", 47 | "test": "vitest run --coverage", 48 | "test:watch": "vitest --ui" 49 | }, 50 | "peerDependencies": { 51 | "agora-rtc-react": ">=2", 52 | "react": ">=16.8" 53 | }, 54 | "devDependencies": { 55 | "agora-rtc-react": "workspace:*" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/assets/UserControl.css: -------------------------------------------------------------------------------- 1 | .arr-user-control { 2 | overflow: hidden; 3 | width: 24px; 4 | height: 24px; 5 | margin: 0; 6 | padding: 0; 7 | border: none; 8 | outline: none; 9 | border-radius: 50%; 10 | color: #fff; 11 | background: rgba(0, 0, 0, 0.5); 12 | transition: background-color 0.4s; 13 | cursor: pointer; 14 | user-select: none; 15 | } 16 | 17 | .arr-user-control:hover, 18 | .arr-user-control:focus, 19 | .arr-user-control:active { 20 | background-color: rgba(0, 0, 0, 0.6); 21 | } 22 | 23 | .arr-user-control:disabled { 24 | cursor: default; 25 | } 26 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/CameraControl.tsx: -------------------------------------------------------------------------------- 1 | import "../assets/UserControl.css"; 2 | 3 | import type { ButtonHTMLAttributes, MouseEvent } from "react"; 4 | import { useCallback } from "react"; 5 | 6 | import { SVGCamera } from "./icons/SVGCamera"; 7 | import { SVGCameraMute } from "./icons/SVGCameraMute"; 8 | 9 | export interface CameraControlProps extends ButtonHTMLAttributes { 10 | /** 11 | * Camera is on. 12 | */ 13 | cameraOn?: boolean; 14 | /** 15 | * Callback when camera is on/off. 16 | */ 17 | onCameraChange?: (cameraOn: boolean) => void; 18 | } 19 | 20 | /** 21 | * A button with camera icon. 22 | */ 23 | export function CameraControl({ 24 | cameraOn, 25 | onCameraChange, 26 | onClick, 27 | className = "", 28 | ...props 29 | }: CameraControlProps) { 30 | const handleClick = useCallback( 31 | (evt: MouseEvent) => { 32 | onCameraChange?.(!cameraOn); 33 | onClick?.(evt); 34 | }, 35 | [onCameraChange, onClick, cameraOn], 36 | ); 37 | 38 | return ( 39 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/CameraVideoTrack.tsx: -------------------------------------------------------------------------------- 1 | import type { LocalVideoTrackProps, VideoPlayerConfig } from "agora-rtc-react"; 2 | import type { ICameraVideoTrack } from "agora-rtc-react"; 3 | import { LocalVideoTrack } from "agora-rtc-react"; 4 | import { useAwaited } from "agora-rtc-react/src/hooks/tools"; 5 | import type { MaybePromiseOrNull } from "agora-rtc-react/src/misc/utils"; 6 | import { useEffect } from "react"; 7 | 8 | export interface CameraVideoTrackProps extends LocalVideoTrackProps { 9 | /** 10 | * A camera video track which can be created by `createCameraVideoTrack()`. 11 | */ 12 | readonly track?: MaybePromiseOrNull; 13 | /** 14 | * Device ID, which can be retrieved by calling `getDevices()`. 15 | */ 16 | readonly deviceId?: string; 17 | 18 | /** 19 | * Playback configurations for a video track. Set the playback configurations for a video track when calling [ILocalVideoTrack.play]{@link ILocalVideoTrack.play}. 20 | */ 21 | readonly videoPlayerConfig?: VideoPlayerConfig; 22 | } 23 | 24 | /** 25 | * A component which renders a camera video track, with device options. 26 | * 27 | * ```jsx 28 | * const track = useMemo(() => AgoraRTC.createCameraVideoTrack(), []) 29 | * return 30 | * ``` 31 | */ 32 | export function CameraVideoTrack({ 33 | track: maybeTrack, 34 | deviceId, 35 | videoPlayerConfig, 36 | ...props 37 | }: CameraVideoTrackProps) { 38 | const track = useAwaited(maybeTrack); 39 | 40 | useEffect(() => { 41 | if (track && deviceId != null) { 42 | track.setDevice(deviceId).catch(console.warn); 43 | } 44 | }, [deviceId, track]); 45 | 46 | return ; 47 | } 48 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/MicControl.tsx: -------------------------------------------------------------------------------- 1 | import "../assets/UserControl.css"; 2 | 3 | import type { ILocalAudioTrack, IRemoteAudioTrack } from "agora-rtc-react"; 4 | import { useVolumeLevel } from "agora-rtc-react"; 5 | import type { ButtonHTMLAttributes, MouseEvent } from "react"; 6 | import { useCallback } from "react"; 7 | 8 | import { SVGMicrophone } from "./icons/SVGMicrophone"; 9 | import { SVGMicrophoneMute } from "./icons/SVGMicrophoneMute"; 10 | 11 | export interface MicControlProps extends ButtonHTMLAttributes { 12 | /** 13 | * Audio track to subscribe volume level. 14 | */ 15 | audioTrack?: ILocalAudioTrack | IRemoteAudioTrack; 16 | /** 17 | * Microphone is on. 18 | */ 19 | micOn?: boolean; 20 | /** 21 | * Callback when microphone is on/off. 22 | */ 23 | onMicChange?: (micOn: boolean) => void; 24 | /** 25 | * Add noise to volume level for a more organic effect. 26 | */ 27 | noise?: number; 28 | } 29 | 30 | /** 31 | * A button with microphone icon. 32 | * Display realtime volume level when `audioTrack` is provided. 33 | */ 34 | export function MicControl({ 35 | noise, 36 | audioTrack, 37 | micOn, 38 | onMicChange, 39 | onClick, 40 | className = "", 41 | ...props 42 | }: MicControlProps) { 43 | const volumeLevel = useVolumeLevel(audioTrack); 44 | 45 | const handleClick = useCallback( 46 | (evt: MouseEvent) => { 47 | onMicChange?.(!micOn); 48 | onClick?.(evt); 49 | }, 50 | [onMicChange, onClick, micOn], 51 | ); 52 | 53 | return ( 54 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/MicrophoneAudioTrack.tsx: -------------------------------------------------------------------------------- 1 | import type { LocalAudioTrackProps } from "agora-rtc-react"; 2 | import type { IMicrophoneAudioTrack } from "agora-rtc-react"; 3 | import { LocalAudioTrack } from "agora-rtc-react"; 4 | import { useAwaited } from "agora-rtc-react/src/hooks/tools"; 5 | import type { MaybePromiseOrNull } from "agora-rtc-react/src/misc/utils"; 6 | import type { ReactNode } from "react"; 7 | import { useEffect } from "react"; 8 | 9 | export interface MicrophoneAudioTrackProps extends LocalAudioTrackProps { 10 | /** 11 | * A microphone audio track which can be created by `createMicrophoneAudioTrack()`. 12 | */ 13 | readonly track?: MaybePromiseOrNull; 14 | /** 15 | * Device ID, which can be retrieved by calling `getDevices()`. 16 | */ 17 | readonly deviceId?: string; 18 | 19 | readonly children?: ReactNode; 20 | } 21 | 22 | /** 23 | * A component which renders a microphone audio track, with device options. 24 | * 25 | * ```jsx 26 | * const track = useMemo(() => AgoraRTC.createMicrophoneAudioTrack(), []) 27 | * return 28 | * ``` 29 | */ 30 | export function MicrophoneAudioTrack({ 31 | track: maybeTrack, 32 | deviceId, 33 | ...props 34 | }: MicrophoneAudioTrackProps) { 35 | const track = useAwaited(maybeTrack); 36 | 37 | useEffect(() => { 38 | if (track && deviceId != null) { 39 | track.setDevice(deviceId).catch(console.warn); 40 | } 41 | }, [deviceId, track]); 42 | 43 | return ; 44 | } 45 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/icons/SVGCamera.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | import { memo } from "react"; 3 | 4 | export type SVGCameraProps = SVGProps; 5 | 6 | export const SVGCamera = /* @__PURE__ */ memo(function SVGCamera(props) { 7 | return ( 8 | 16 | 17 | 18 | 19 | 25 | 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/icons/SVGCameraMute.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | import { memo } from "react"; 3 | 4 | export type SVGCameraMuteProps = SVGProps; 5 | 6 | export const SVGCameraMute = /* @__PURE__ */ memo(function SVGCameraMute( 7 | props, 8 | ) { 9 | return ( 10 | 18 | 24 | 30 | 31 | ); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/icons/SVGMicrophoneMute.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | import { memo } from "react"; 3 | 4 | export type SVGMicrophoneMuteProps = SVGProps; 5 | 6 | export const SVGMicrophoneMute = /* @__PURE__ */ memo( 7 | function SVGMicrophoneMute(props) { 8 | return ( 9 | 17 | 23 | 29 | 30 | ); 31 | }, 32 | ); 33 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SVGMicrophone"; 2 | export * from "./SVGMicrophoneMute"; 3 | export * from "./SVGCamera"; 4 | export * from "./SVGCameraMute"; 5 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./icons"; 2 | export * from "./MicControl"; 3 | export * from "./CameraControl"; 4 | export * from "./MicrophoneAudioTrack"; 5 | export * from "./CameraVideoTrack"; 6 | export * from "./LocalMicrophoneAndCameraUser"; 7 | export * from "./RemoteVideoPlayer"; 8 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components"; 2 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/stories/CameraControl.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | 3 | import type { CameraControlProps } from "../components"; 4 | import { CameraControl } from "../components"; 5 | 6 | const meta: Meta = { 7 | title: "Controls/CameraControl", 8 | component: CameraControl, 9 | tags: ["autodocs"], 10 | parameters: { 11 | backgrounds: { default: "light" }, 12 | }, 13 | }; 14 | 15 | export default meta; 16 | 17 | export const CameraOn: StoryObj = { 18 | args: { 19 | cameraOn: true, 20 | }, 21 | }; 22 | 23 | export const CameraOff: StoryObj = { 24 | args: {}, 25 | }; 26 | 27 | export const RemoteCameraOn: StoryObj = { 28 | args: { 29 | cameraOn: true, 30 | disabled: true, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/stories/CameraVideoTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeCameraVideoTrack } from "agora-rtc-sdk-ng-fake"; 3 | import { useState } from "react"; 4 | 5 | import type { CameraVideoTrackProps } from "../components"; 6 | import { CameraVideoTrack } from "../components"; 7 | 8 | const meta: Meta = { 9 | title: "Track/CameraVideoTrack", 10 | component: CameraVideoTrack, 11 | tags: ["autodocs"], 12 | parameters: { 13 | layout: "fullscreen", 14 | }, 15 | argTypes: { 16 | track: { 17 | control: { 18 | type: null, 19 | }, 20 | }, 21 | }, 22 | render: function RenderCameraVideoTrack(args) { 23 | const [track] = useState(() => (args.track ? FakeCameraVideoTrack.create() : undefined)); 24 | return ; 25 | }, 26 | }; 27 | 28 | export default meta; 29 | 30 | export const Enabled: StoryObj = { 31 | args: { 32 | track: FakeCameraVideoTrack.create(), 33 | play: true, 34 | }, 35 | }; 36 | 37 | export const EmptyTrack: StoryObj = { 38 | args: { 39 | play: true, 40 | children:

An Empty Track

, 41 | }, 42 | }; 43 | 44 | export const Disabled: StoryObj = { 45 | args: { 46 | track: FakeCameraVideoTrack.create(), 47 | play: true, 48 | disabled: true, 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/stories/MicControl.stories.tsx: -------------------------------------------------------------------------------- 1 | import { randNumber } from "@ngneat/falso"; 2 | import type { Meta, StoryObj } from "@storybook/react"; 3 | import { FakeLocalAudioTrack, FakeRemoteAudioTrack } from "agora-rtc-sdk-ng-fake"; 4 | import { useEffect } from "react"; 5 | 6 | import type { MicControlProps } from "../components"; 7 | import { MicControl } from "../components"; 8 | 9 | const meta: Meta = { 10 | title: "Controls/MicControl", 11 | component: MicControl, 12 | tags: ["autodocs"], 13 | parameters: { 14 | backgrounds: { default: "light" }, 15 | }, 16 | decorators: [ 17 | (Story, context) => { 18 | const audioTrack = context.args.audioTrack; 19 | useEffect(() => { 20 | if (audioTrack) { 21 | const ticket = setInterval(() => { 22 | audioTrack.setVolume(randNumber({ min: 0, max: 100 })); 23 | }, 2000); 24 | return () => clearInterval(ticket); 25 | } 26 | }, [audioTrack]); 27 | return Story(); 28 | }, 29 | ], 30 | }; 31 | 32 | export default meta; 33 | 34 | export const MicOn: StoryObj = { 35 | args: { 36 | audioTrack: FakeLocalAudioTrack.create(), 37 | micOn: true, 38 | }, 39 | }; 40 | 41 | export const MicOff: StoryObj = { 42 | args: { 43 | audioTrack: FakeLocalAudioTrack.create(), 44 | }, 45 | }; 46 | 47 | export const RemoteMicOn: StoryObj = { 48 | args: { 49 | audioTrack: FakeRemoteAudioTrack.create(), 50 | micOn: true, 51 | disabled: true, 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/stories/MicrophoneAudioTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeMicrophoneAudioTrack } from "agora-rtc-sdk-ng-fake"; 3 | 4 | import type { MicrophoneAudioTrackProps } from "../components"; 5 | import { MicrophoneAudioTrack } from "../components"; 6 | 7 | const meta: Meta = { 8 | title: "Track/MicrophoneAudioTrack", 9 | component: MicrophoneAudioTrack, 10 | tags: ["autodocs"], 11 | parameters: { 12 | layout: "fullscreen", 13 | }, 14 | argTypes: { 15 | track: { 16 | control: { 17 | type: null, 18 | }, 19 | }, 20 | }, 21 | render(args) { 22 | return ( 23 | 24 |

An Example Microphone Audio Track

25 |
26 | ); 27 | }, 28 | }; 29 | 30 | export default meta; 31 | 32 | export const Enabled: StoryObj = { 33 | args: { 34 | track: FakeMicrophoneAudioTrack.create(), 35 | play: true, 36 | }, 37 | }; 38 | 39 | export const EmptyTrack: StoryObj = { 40 | args: { 41 | play: true, 42 | }, 43 | }; 44 | 45 | export const Disabled: StoryObj = { 46 | args: { 47 | track: FakeMicrophoneAudioTrack.create(), 48 | play: true, 49 | disabled: true, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/test/MicrophoneAudioTrack.test.tsx: -------------------------------------------------------------------------------- 1 | import { composeStories } from "@storybook/react"; 2 | import { render } from "@testing-library/react"; 3 | import type { IMicrophoneAudioTrack } from "agora-rtc-react"; 4 | import { useAwaited } from "agora-rtc-react/src/hooks/tools"; 5 | import type { Mock } from "vitest"; 6 | import { describe, expect, test, vi } from "vitest"; 7 | 8 | import { MicrophoneAudioTrack } from "../src/components"; 9 | import * as stories from "../src/stories/MicrophoneAudioTrack.stories"; 10 | const { Enabled } = composeStories(stories); 11 | 12 | vi.mock("agora-rtc-react/src/hooks/tools", () => ({ 13 | useAwaited: vi.fn(), 14 | useIsomorphicLayoutEffect: vi.fn(), 15 | })); 16 | const mockTrack: IMicrophoneAudioTrack = { 17 | setDevice: vi.fn().mockReturnValue(Promise.resolve()), 18 | } as unknown as IMicrophoneAudioTrack; 19 | const mockUseAwaited = useAwaited as Mock; 20 | 21 | describe("MicrophoneAudioTrack component", () => { 22 | test("renders without crashing", () => { 23 | mockUseAwaited.mockReturnValueOnce(mockTrack); 24 | const { container } = render(); 25 | expect(container).toBeInTheDocument(); 26 | vi.clearAllMocks(); 27 | }); 28 | 29 | test("sets device ID on audio track", () => { 30 | mockUseAwaited.mockReturnValueOnce(mockTrack); 31 | const deviceId = "mockDeviceId"; 32 | render(); 33 | expect(mockTrack.setDevice).toHaveBeenCalledTimes(1); 34 | expect(mockTrack.setDevice).toHaveBeenCalledWith(deviceId); 35 | vi.clearAllMocks(); 36 | }); 37 | }); 38 | 39 | describe("MicrophoneAudioTrack component stories", () => { 40 | test("renders Enabled stories", async () => { 41 | const { getByText } = render(); 42 | expect(getByText("An Example Microphone Audio Track")).toBeInTheDocument(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/test/setup.tsx: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | import type { ReactNode } from "react"; 3 | import { vi } from "vitest"; 4 | import "vitest-canvas-mock"; 5 | 6 | /** 7 | * started agora-rtc-sdk-ng@17.0.0, need mock RTCPeerConnection. 8 | * RTCPeerConnection does not implement global 9 | */ 10 | // @ts-expect-error type 11 | global.RTCPeerConnection = vi.fn(); 12 | 13 | /** 14 | * JSDOM does not implement global "HTMLMediaElement.prototype.play" function 15 | */ 16 | HTMLMediaElement.prototype.play = vi.fn().mockReturnValue(Promise.resolve()); 17 | HTMLMediaElement.prototype.pause = vi.fn().mockReturnValue(Promise.resolve()); 18 | 19 | /** 20 | * navigator does not implement global "mediaDevices.prototype.getUserMedia" function 21 | * navigator does not implement global "mediaDevices.prototype.enumerateDevices" function 22 | * 23 | */ 24 | const mockPromise = vi.fn(async () => { 25 | return new Promise(resolve => { 26 | resolve(); 27 | }); 28 | }); 29 | Object.defineProperty(global.navigator, "mediaDevices", { 30 | value: { 31 | getUserMedia: mockPromise, 32 | enumerateDevices: mockPromise, 33 | }, 34 | }); 35 | 36 | export interface Props { 37 | children: ReactNode; 38 | } 39 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "noFallthroughCasesInSwitch": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "noUnusedParameters": true, 8 | "noImplicitOverride": true, 9 | "module": "ESNext", 10 | "target": "ESNext", 11 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 12 | "moduleResolution": "Node", 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "jsx": "react-jsx" 17 | }, 18 | "include": ["src", "test"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | import setGlobals from "../../scripts/tsup/set-globals"; 4 | 5 | import pkg from "./package.json"; 6 | 7 | const banner = ` 8 | /** 9 | * @license ${pkg.name} 10 | * @version ${pkg.version} 11 | * 12 | * Copyright (c) Agora, Inc. 13 | * 14 | * This source code is licensed under the MIT license. 15 | */ 16 | `; 17 | 18 | export default defineConfig([ 19 | { 20 | entry: { 21 | [pkg.name]: "src/index.ts", 22 | }, 23 | banner: () => { 24 | return { 25 | js: banner, 26 | }; 27 | }, 28 | format: ["cjs", "esm"], 29 | splitting: false, 30 | sourcemap: false, 31 | clean: true, 32 | treeshake: true, 33 | dts: true, 34 | minify: false, 35 | }, 36 | { 37 | entry: { 38 | [pkg.name]: "src/index.ts", 39 | }, 40 | banner: () => { 41 | return { 42 | js: banner, 43 | }; 44 | }, 45 | outExtension: () => { 46 | return { 47 | js: `.iife.js`, 48 | }; 49 | }, 50 | format: ["iife"], 51 | sourcemap: false, 52 | splitting: false, 53 | clean: true, 54 | minify: true, 55 | external: Object.keys(pkg.peerDependencies), 56 | define: { 57 | "process.env.NODE_ENV": JSON.stringify("production"), 58 | }, 59 | globalName: "AgoraRTCReactUIKit", 60 | esbuildPlugins: [ 61 | setGlobals({ 62 | "react": "React", 63 | "react-dom": "ReactDOM", 64 | "agora-rtc-sdk-ng": "AgoraRTC", 65 | }), 66 | ], 67 | platform: "browser", 68 | }, 69 | ]); 70 | -------------------------------------------------------------------------------- /packages/agora-rtc-react-ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /// 4 | 5 | import react from "@vitejs/plugin-react"; 6 | import { defineConfig } from "vite"; 7 | 8 | export default defineConfig({ 9 | plugins: [react()], 10 | test: { 11 | onConsoleLog(log) { 12 | if (log.includes("Agora RTC client not found")) return false; 13 | if (log.includes("Agora-SDK [DEBUG]: ")) return false; 14 | if (log.includes("Agora-SDK [WARNING]: ")) return false; 15 | if (log.includes("Agora-SDK [ERROR]: ")) return false; 16 | if (log.includes("Agora-SDK [INFO]: ")) return false; 17 | if (log.includes("Agora-RTC-REACT [ERROR_TEST_MSG]")) return false; 18 | }, 19 | environment: "jsdom", 20 | globals: true, 21 | coverage: { 22 | provider: "v8", 23 | reporter: ["text", "json", "html", "lcov", "json-summary"], 24 | exclude: ["test/**", "src/stories/*", "src/**/index.ts"], 25 | }, 26 | exclude: ["**/node_modules/**"], 27 | deps: { 28 | inline: ["vitest-canvas-mock"], 29 | }, 30 | setupFiles: ["../shared/test/setup.tsx"], 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | const { mergeConfig } = require("vite"); 2 | 3 | module.exports = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-essentials", 7 | "@storybook/addon-links", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/react-vite", 12 | options: {}, 13 | }, 14 | docs: { 15 | autodocs: "tag", 16 | defaultName: "Docs", // doc name 17 | }, 18 | core: { 19 | disableTelemetry: true, 20 | }, 21 | typescript: { 22 | reactDocgen: "react-docgen", 23 | skipBabel: true, 24 | check: false, 25 | }, 26 | async viteFinal(config) { 27 | return mergeConfig(config, { 28 | resolve: { 29 | alias: { 30 | "agora-rtc-sdk-ng": require.resolve("agora-rtc-sdk-ng-fake/src/index.ts"), 31 | }, 32 | }, 33 | }); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import "../../shared/assets/storybook/global.scss"; 2 | 3 | export const parameters = { 4 | backgrounds: { 5 | default: "white", 6 | values: [ 7 | { 8 | name: "white", 9 | value: "#fff", 10 | }, 11 | { 12 | name: "light", 13 | value: "rgb(248, 248, 248)", 14 | }, 15 | { 16 | name: "grey", 17 | value: "#555", 18 | }, 19 | { 20 | name: "dark", 21 | value: "rgb(25, 27, 31)", 22 | }, 23 | ], 24 | }, 25 | actions: { argTypesRegex: "^on[A-Z].*" }, 26 | controls: { 27 | expanded: true, 28 | matchers: { 29 | color: /(background|color)$/i, 30 | date: /Date$/, 31 | }, 32 | }, 33 | options: { 34 | showPanel: true, 35 | }, 36 | docs: { 37 | canvas: { 38 | sourceState: "shown", 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/components/AgoraRTCProvider.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCProvider 2 | 3 | This component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context), which lets all of the components inside `children` read the `client` prop you pass. 4 | 5 | #### Props 6 | 7 | | Prop | Type | Default value | Description | 8 | | ---------- | -------------------------------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | None | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | | `children` | `ReactNode` | None | The React nodes to be rendered. | 11 | 12 | #### Sample code 13 | 14 | ```jsx 15 | import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react"; 16 | 17 | function App({ children }) { 18 | const [client] = useState(() => AgoraRTC.createClient({ mode: "rtc", codec: "vp8" })); 19 | return {children}; 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/components/AgoraRTCProvider.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCProvider 2 | 3 | 该组件向子组件[提供 Context](https://react.dev/learn/passing-data-deeply-with-context),让 `children` 内的各个组件都能读取你传入的 `client` 属性。 4 | 5 | #### Props 6 | 7 | | 属性名 | 类型 | 默认值 | 描述 | 8 | | ---------- | ----------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | 无 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | | `children` | `ReactNode` | 无 | 需要展示的 React 节点。 | 11 | 12 | #### 使用示例 13 | 14 | ```jsx 15 | import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react"; 16 | 17 | function App({ children }) { 18 | const [client] = useState(() => AgoraRTC.createClient({ mode: "rtc", codec: "vp8" })); 19 | return {children}; 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/components/AgoraRTCScreenShareProvider.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCScreenShareProvider 2 | 3 | This component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context), which lets all of the components inside `children` read the `client` prop you pass for screen sharing. 4 | 5 | #### Props 6 | 7 | | Prop | Type | Default value | Description | 8 | | ---------- | -------------------------------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | None | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | | `children` | `ReactNode` | None | The React nodes to be rendered. | 11 | 12 | #### Caveats 13 | 14 | You can use `AgoraRTCScreenShareProvider` and `AgoraRTCProvider` together, but they do not share the `client` prop. 15 | 16 | #### Sample code 17 | 18 | ```jsx 19 | import AgoraRTC, { AgoraRTCScreenShareProvider } from "agora-rtc-react"; 20 | 21 | function App({ children }) { 22 | const [client] = useState(() => AgoraRTC.createClient({ mode: "rtc", codec: "vp8" })); 23 | return {children}; 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/components/AgoraRTCScreenShareProvider.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCScreenShareProvider 2 | 3 | 该组件向子组件[提供 Context](https://react.dev/learn/passing-data-deeply-with-context),让 `children` 内的各个组件都能读取你传入的用于屏幕共享的 `client` 属性。 4 | 5 | #### Props 6 | 7 | | 属性名 | 类型 | 默认值 | 描述 | 8 | | ---------- | ----------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | 无 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | | `children` | `ReactNode` | 无 | 需要展示的 React 节点。 | 11 | 12 | #### 注意事项 13 | 14 | 你可以同时使用 `AgoraRTCScreenShareProvider` 和 `AgoraRTCProvider` ,但二者不会共享 `client` 属性。 15 | 16 | #### 使用示例 17 | 18 | ```jsx 19 | import AgoraRTC, { AgoraRTCScreenShareProvider } from "agora-rtc-react"; 20 | 21 | function App({ children }) { 22 | const [client] = useState(() => AgoraRTC.createClient({ mode: "rtc", codec: "vp8" })); 23 | return {children}; 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/components/RemoteVideoTrack.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## RemoteVideoTrack 2 | 3 | 该组件用于播放远端用户的视频轨道,并且不支持指定播放设备。 4 | 5 | #### Props 6 | 7 | | 属性名 | 类型 | 默认值 | 描述 | 8 | | ------------------- | --------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `track` | [`IRemoteVideoTrack`](/api-ref/rtc/react/interfaces/iremotevideotrack.html) | 无 | 远端视频轨道对象。 | 10 | | `play` | `boolean` | 无 |
  • `true`:播放该轨道。
  • `false`:停止播放该轨道。
  • | 11 | | `videoPlayerConfig` | `VideoPlayerConfig` | 无 | 视频轨道的播放配置。可以设置播放参数(镜像/显示模式),详见 [`VideoPlayerConfig`](/api-ref/rtc/react/interfaces/videoplayerconfig.html)。 | 12 | 13 | #### 使用示例 14 | 15 | ```jsx 16 | import { RemoteAudioTrack, useJoin, useRemoteAudioTracks, useRemoteUsers } from "agora-rtc-react"; 17 | 18 | function App() { 19 | const remoteUsers = useRemoteUsers(); 20 | const audioTracks = useRemoteAudioTracks(remoteUsers); 21 | 22 | return ( 23 | <> 24 | {audioTracks.map(track => ( 25 | 26 | ))} 27 | 28 | ); 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/data-types/AgoraRTCReactError.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCReactError 2 | 3 | Thrown errors. 4 | 5 | `AgoraRTCReactError` extends the browser's [Error object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). When you directly print the `AgoraRTCReactError` object, you can see the error message. 6 | 7 | | Property | Type | Required | Description | 8 | | ----------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `rtcMethod` | `string` | Yes | The Web SDK method that throws the error, which helps you determine the corresponding Hook where the error occurred. See the table below. | 10 | 11 | **Mapping of `rtcMethod` to Hooks** 12 | 13 | | `rtcMethod` | Corresponding Hook | 14 | | ------------------------------------------------------------------------------------------------------- | ------------------------- | 15 | | `"IAgoraRTCClient.join"` | `useJoin` | 16 | | `"IAgoraRTC.createCameraVideoTrack" ` | `useLocalCameraTrack` | 17 | | `"IAgoraRTC.createMicrophoneAudioTrack"` | `useLocalMicrophoneTrack` | 18 | | `"IAgoraRTCClient.publish"` | `usePublish` | 19 | | `"IAgoraRTCClient.unsubscribe"` or `"IAgoraRTCClient.subscribe"` | `useRemoteUserTrack` | 20 | | `"IAgoraRTCClient.unsubscribe"` or `"IAgoraRTCClient.subscribe"` or `"IAgoraRTCClient.massUnsubscribe"` | `useRemoteVideoTracks ` | 21 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/data-types/AgoraRTCReactError.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## AgoraRTCReactError 2 | 3 | 抛出的错误。 4 | 5 | `AgoraRTCReactError` 继承自浏览器的 [Error 对象](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)。直接打印 `AgoraRTCReactError` 对象可以看到错误信息。 6 | 7 | | 属性 | 类型 | 是否必选 | 描述 | 8 | | ----------- | -------- | -------- | ----------------------------------------------------------------------------------------- | 9 | | `rtcMethod` | `string` | 必选 | 发生错误的 Web SDK 方法名。根据 Web SDK 的方法名可以定位发生错误的 Hook,对应关系见下表。 | 10 | 11 | **`rtcMethod` 与 Hook 的关系表** 12 | 13 | | `rtcMethod` | 发生错误的 Hook | 14 | | ------------------------------------------------------------------------------------------------------- | ------------------------- | 15 | | `"IAgoraRTCClient.join"` | `useJoin` | 16 | | `"IAgoraRTC.createCameraVideoTrack" ` | `useLocalCameraTrack` | 17 | | `"IAgoraRTC.createMicrophoneAudioTrack"` | `useLocalMicrophoneTrack` | 18 | | `"IAgoraRTCClient.publish"` | `usePublish` | 19 | | `"IAgoraRTCClient.unsubscribe"` 或 `"IAgoraRTCClient.subscribe"` | `useRemoteUserTrack` | 20 | | `"IAgoraRTCClient.unsubscribe"` 或 `"IAgoraRTCClient.subscribe"` 或 `"IAgoraRTCClient.massUnsubscribe"` | `useRemoteVideoTracks ` | 21 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/data-types/JoinOptions.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## JoinOptions 2 | 3 | 加入频道所需参数。 4 | 5 | | 属性 | 类型 | 是否必选 | 描述 | 6 | | --------- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 7 | | `appid` | `string` | 必选 | 声网项目的 App ID。 | 8 | | `channel` | `string` | Yes | 要加入的频道名称。详见 [`IAgoraRTCClient.join`](/api-ref/rtc/react/interfaces/iagorartcclient.html#join) 的参数说明。 | 9 | | `token` | `string` | `null` | Yes | 用于鉴权的 Token,如果你的声网项目启用了 Token 鉴权机制,则需要提供有效的 Token;如果没有启用 Token 鉴权,则传入 `null`。详见 [`IAgoraRTCClient.join`](/api-ref/rtc/react/interfaces/iagorartcclient.html#join) 的参数说明。 | 10 | | `uid` | `UID` |`null` | No | 用户 ID,如果不提供则由服务器自动生成一个 number 型的 `uid`。详见 [`IAgoraRTCClient.join`](/api-ref/rtc/react/interfaces/iagorartcclient.html#join) 的参数说明。 | 11 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/data-types/NetworkQuality.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## NetworkQuality 2 | 3 | The last-mile network quality. 4 | 5 | This interface inherits [`NetworkQuality`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/networkquality.html) from the Web SDK and adds the following additional properties: 6 | 7 | | Property | Type | Required | Description | 8 | | -------- | -------- | -------- | ------------------------------------------------------------------------------------------------------- | 9 | | `delay` | `number` | Yes | The average Round-Trip Time (RTT) from the SDK to the Agora edge server, measured in milliseconds (ms). | 10 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/data-types/NetworkQualityEx.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## NetworkQualityEx 2 | 3 | 上下行 last mile 网络质量。 4 | 5 | 该接口类继承自 Web SDK 的 [`NetworkQuality`](/api-ref/rtc/react/interfaces/networkquality.html),并新增以下属性: 6 | 7 | | 属性 | 类型 | 是否必选 | 描述 | 8 | | ------- | -------- | -------- | --------------------------------------------------------------- | 9 | | `delay` | `number` | 必选 | SDK 到声网边缘服务器的平均往返时延(Round-Trip Time),单位 ms。 | 10 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useAutoPlayAudioTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useAutoPlayAudioTrack 2 | 3 | This hook lets you automatically play a local or remote audio track. 4 | 5 | - When the component is mounted, this hook determines whether to automatically play the track according to the `play` parameter. 6 | - When the component is unmounted, this hook stops playing the `track`. 7 | 8 | #### Parameters 9 | 10 | | Parameter | Type | Required | Description | 11 | | --------- | ----------------------------------------------------- | -------- | ------------------------------------------------------------------------- | 12 | | `track` | `IRemoteAudioTrack` | `ILocalAudioTrack` | Yes | The local or remote audio track. | 13 | | `play` | `boolean` | No |
  • `true`: Play the track.
  • `false`: Stop playing the track.
  • | 14 | 15 | #### Returns 16 | 17 | None. 18 | 19 | #### Sample code 20 | 21 | ```jsx 22 | import { useAutoPlayAudioTrack, useLocalMicrophoneTrack } from "agora-rtc-react"; 23 | 24 | function App() { 25 | const audioTrack = useLocalMicrophoneTrack(); 26 | useAutoPlayAudioTrack(track, play); 27 | 28 | return <>; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useAutoPlayAudioTrack.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useAutoPlayAudioTrack 2 | 3 | 用于自动播放本地或远端音频轨道。 4 | 5 | - 当组件挂载时,该 Hook 会根据传入的 `play` 判断是否自动播放。 6 | - 当组件卸载时,该 Hook 会停止播放 `track` 对应的音频轨道。 7 | 8 | #### 参数 9 | 10 | | 参数名 | 类型 | 是否必选 | 描述 | 11 | | ------- | ----------------------------------------------------- | -------- | --------------------------------------------------------------- | 12 | | `track` | `IRemoteAudioTrack` | `ILocalAudioTrack` | 必选 | 远端音频轨道对象或本地音频轨道对象。 | 13 | | `play` | `boolean` | 可选 |
  • `true`:播放该轨道。
  • `false`:停止播放该轨道。
  • | 14 | 15 | #### 返回值 16 | 17 | 无。 18 | 19 | #### 使用示例 20 | 21 | ```jsx 22 | import { useAutoPlayAudioTrack, useLocalMicrophoneTrack } from "agora-rtc-react"; 23 | 24 | function App() { 25 | const audioTrack = useLocalMicrophoneTrack(); 26 | useAutoPlayAudioTrack(track, play); 27 | 28 | return <>; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useAutoPlayVideoTrack.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useAutoPlayVideoTrack 2 | 3 | 用于自动播放本地或远端视频轨道。 4 | 5 | - 当组件挂载时,该 Hook 会根据传入的 `play` 判断是否自动播放。 6 | - 当组件卸载时,该 Hook 会停止播放 `track` 对应的视频轨道。 7 | 8 | #### 参数 9 | 10 | | 参数名 | 类型 | 是否必选 | 描述 | 11 | | ------------------- | ----------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 12 | | `track` | `IRemoteVideoTrack` | `ILocalVideoTrack` | 必选 | 远端视频轨道对象或本地视频轨道对象。 | 13 | | `play` | `boolean` | 可选 |
  • `true`:播放该轨道。
  • `false`:停止播放该轨道。
  • | 14 | | `videoPlayerConfig` | `VideoPlayerConfig` | 可选 | 视频轨道的播放配置。可以设置播放参数(镜像/显示模式),详见 [`VideoPlayerConfig`](/api-ref/rtc/react/interfaces/videoplayerconfig.html)。对于本地视频轨道,镜像模式默认开启。 | 15 | | `div` | `HTMLElement` | `null` | 可选 | 用于渲染视频轨道的 HTML 元素。只有在 `play` 为 `true` 并且传入 `div` 参数的情况下,视频才会在该元素中自动播放。其它情况则不会自动播放视频。 | 16 | 17 | #### 返回值 18 | 19 | 无。 20 | 21 | #### 使用示例 22 | 23 | ```jsx 24 | import { useAutoPlayVideoTrack, useLocalCameraTrack } from "agora-rtc-react"; 25 | 26 | function App() { 27 | const videoTrack = useLocalCameraTrack(); 28 | useAutoPlayVideoTrack(track, play, div); 29 | 30 | return <>; 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useClientEvent.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useClientEvent 2 | 3 | 用于监听 `IAgoraRTCClient` 对象的指定事件。 4 | 5 | - 当组件挂载时,该 Hook 会注册对应的事件监听器。 6 | - 当组件卸载时,该 Hook 会销毁对应的事件监听器。 7 | 8 | #### 参数 9 | 10 | | 参数名 | 类型 | 是否必选 | 描述 | 11 | | ---------- | ----------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 12 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | 必选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 13 | | `event` | `string` | 必选 | 事件名称。支持的事件详见[`IAgoraRTCClient.on`](/api-ref/rtc/react/interfaces/iagorartcclient.html?platform=All%20Platforms#on)。 | 14 | | `listener` | `Function` | 必选 | 回调函数。当传入的事件触发时,该函数会被调用。支持的回调详见 [`IAgoraRTCClient.on`](/api-ref/rtc/react/interfaces/iagorartcclient.html?platform=All%20Platforms#on)。 | 15 | 16 | #### 返回值 17 | 18 | 无。 19 | 20 | #### 使用示例 21 | 22 | ```jsx 23 | import { useRTCClient, useClientEvent } from "agora-rtc-react"; 24 | 25 | function App() { 26 | const client = useRTCClient(); 27 | useClientEvent(client, "connection-state-change", () => {}); 28 | 29 | return <>; 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useConnectionState.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useConnectionState 2 | 3 | Returns the detailed connection state of the SDK. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 15 | | `ConnectionState` | The connection state between the SDK and Agora's edge server. See [`ConnectionState`](https://api-ref.agora.io/en/video-sdk/web/4.x/globals.html#connectionstate) for details. | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useConnectionState } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const connectionState = useConnectionState(); 24 | 25 | return
    {connectionState}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useConnectionState.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useConnectionState 2 | 3 | 用于获取详细的 SDK 连接状态。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | -------- | --------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | ----------------- | --------------------------------------------------------------------------------------------------- | 15 | | `ConnectionState` | SDK 与服务器的连接状态。详见 [`ConnectionState`](/api-ref/rtc/react/globals.html#connectionstate)。 | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useConnectionState } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const connectionState = useConnectionState(); 24 | 25 | return
    {connectionState}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useCurrentUID.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useCurrentUID 2 | 3 | Returns the current user ID. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | -------------------------------- | --------------------------------------------------------------------------------------------------------- | 15 | | `UID` | `undefined` | The user ID of the current user. If the current user has not joined any channel, `undefined` is returned. | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useCurrentUID } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const uid = useCurrentUID(); 24 | 25 | return
    {uid}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useCurrentUID.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useCurrentUID 2 | 3 | 用于获取当前用户 UID 。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | -------- | --------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | -------------------------------- | ------------------------------------------------------------------ | 15 | | `UID` | `undefined` | 当前用户的 UID。如果当前用户没有加入任何频道,则返回 `undefined`。 | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useCurrentUID } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const uid = useCurrentUID(); 24 | 25 | return
    {uid}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useIsConnected.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useIsConnected 2 | 3 | Returns whether the SDK is connected to Agora's server. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | --------- | ------------------------------------------------------------------------------------------------------------- | 15 | | `boolean` |
  • `true`: The SDK is connected to the server.
  • `false`: The SDK is not connected to the server.
  • | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useIsConnected } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const isConnected = useIsConnected(); 24 | 25 | return
    {isConnected}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useIsConnected.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useIsConnected 2 | 3 | 用于获取 SDK 是否连接到服务器。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | -------- | --------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | --------- | ----------------------------------------------------------------------------- | 15 | | `boolean` |
  • `true`:SDK 已连接到服务器。
  • `false`:SDK 没有连接到服务器。
  • | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useIsConnected } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const isConnected = useIsConnected(); 24 | 25 | return
    {isConnected}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useNetworkQuality.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useNetworkQuality 2 | 3 | Returns the network quality of the local user. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 15 | | `NetworkQuality` | The network quality of the local user. See [`NetworkQuality`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/interfaces/NetworkQuality.html) for details. | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useNetworkQuality } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const networkQuality = useNetworkQuality(); 24 | 25 | return
    {networkQuality}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useNetworkQuality.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useNetworkQuality 2 | 3 | 用于获取本地用户网络质量。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | -------- | --------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | 15 | | `NetworkQuality` | 本地用户的网络质量信息。详见 [`NetworkQuality`](https://doc.shengwang.cn/api-ref/rtc/react/react-sdk/data-types#networkquality)。 | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useNetworkQuality } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const networkQuality = useNetworkQuality(); 24 | 25 | return
    {networkQuality}
    ; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useRTCClient.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useRTCClient 2 | 3 | Returns the `IAgoraRTCClient` object. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | If provided, the passed `IAgoraRTCClient` object is returned. If not provided, the `IAgoraRTCClient` object obtained from the [parent component's context](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/AgoraRTCProvider.html) is returned. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | -------------------------------------------------------------------------------------------------- | ----------------------------- | 15 | | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | The `IAgoraRTCClient` client. | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useRTCClient } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const client = useRTCClient(); 24 | 25 | return <>; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useRTCClient.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useRTCClient 2 | 3 | 用于获取 `IAgoraRTCClient` 对象。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | -------- | --------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 如果传入该参数,则使用传入的 `IAgoraRTCClient` 对象;如果不传入该参数,则使用从[父组件的 Context](https://doc.shengwang.cn/api-ref/rtc/react/react-sdk/components#agorartcprovider)中获取的 `IAgoraRTCClient` 对象。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | ----------------------------------------------------------------------- | ------------------------ | 15 | | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `IAgoraRTCClient` 对象。 | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useRTCClient } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const client = useRTCClient(); 24 | 25 | return <>; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useRemoteUsers.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useRemoteUsers 2 | 3 | This hook lets you retrieve the list of remote users. 4 | 5 | The return value of this hook is updated in the following cases: 6 | 7 | - When a remote user joins or leaves the channel. 8 | - When the role of a remote user changes (for example, from broadcaster to audience). 9 | - When a remote user publishes or unpublishes the audio or video track. 10 | 11 | #### Parameters 12 | 13 | | Parameter | Type | Required | Description | 14 | | --------- | ------------------------------------------------------------------------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 15 | | `client` | [`IAgoraRTCClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html) | `null` | No | Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. | 16 | 17 | #### Returns 18 | 19 | | Type | Description | 20 | | ----------------------- | ------------------------- | 21 | | `IAgoraRTCRemoteUser[]` | The list of remote users. | 22 | 23 | #### Sample code 24 | 25 | ```jsx 26 | import { useRemoteUsers } from "agora-rtc-react"; 27 | 28 | function App() { 29 | const remoteUsers = useRemoteUsers(); 30 | 31 | return <>; 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useRemoteUsers.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useRemoteUsers 2 | 3 | 用于获取远端用户列表。 4 | 5 | 发生以下情况时,该 Hook 的返回值会更新: 6 | 7 | - 远端用户加入或离开频道。 8 | - 远端用户的角色改变(比如从主播变为观众)。 9 | - 远端用户发布或取消发布音频或视频轨道。 10 | 11 | #### 参数 12 | 13 | | 参数名 | 类型 | 是否必选 | 描述 | 14 | | -------- | --------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | 15 | | `client` | [`IAgoraRTCClient`](/api-ref/rtc/react/interfaces/iagorartcclient.html) | `null` | 可选 | 通过 Web SDK 的 [`IAgoraRTC.createClient`](/api-ref/rtc/react/interfaces/iagorartc.html#createclient) 创建。 | 16 | 17 | #### 返回值 18 | 19 | | 类型 | 描述 | 20 | | ----------------------- | -------------- | 21 | | `IAgoraRTCRemoteUser[]` | 远端用户列表。 | 22 | 23 | #### 使用示例 24 | 25 | ```jsx 26 | import { useRemoteUsers } from "agora-rtc-react"; 27 | 28 | function App() { 29 | const remoteUsers = useRemoteUsers(); 30 | 31 | return <>; 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useVolumeLevel.en-US.mdx: -------------------------------------------------------------------------------- 1 | ## useVolumeLevel 2 | 3 | Returns the volume level of an audio track at a frequency of once per second. 4 | 5 | #### Parameters 6 | 7 | | Parameter | Type | Required | Description | 8 | | ------------ | -------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `audioTrack` | `IRemoteAudioTrack` | `ILocalAudioTrack` | `undefined` | No | The local or remote audio track. The local audio track can be created by calling [`useLocalMicrophoneTrack`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useLocalMicrophoneTrack.html). If undefined, the volume level is 0. | 10 | 11 | #### Returns 12 | 13 | | Type | Description | 14 | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | 15 | | `number` | The volume level. The value range is [0,1]. 1 is the highest volume level. Usually a user with a volume level above 0.6 is a speaking user. | 16 | 17 | #### Sample code 18 | 19 | ```jsx 20 | import { useVolumeLevel, useLocalMicrophoneTrack } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const audioTrack = useLocalMicrophoneTrack(); 24 | const volumeLevel = useVolumeLevel(audioTrack); 25 | 26 | return
    {volumeLevel}
    ; 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/docs/hooks/useVolumeLevel.zh-CN.mdx: -------------------------------------------------------------------------------- 1 | ## useVolumeLevel 2 | 3 | 用于自动获取音频轨道音量级别,自动获取的频率为每秒一次。 4 | 5 | #### 参数 6 | 7 | | 参数名 | 类型 | 是否必选 | 描述 | 8 | | ------------ | -------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `audioTrack` | `IRemoteAudioTrack` | `ILocalAudioTrack` | `undefined` | 可选 | 本地或远端音频轨道,其中本地音频轨道通过 [`useLocalMicrophoneTrack`](https://doc.shengwang.cn/api-ref/rtc/react/react-sdk/hooks#uselocalmicrophonetrack) 创建。如果未定义,则音量级别为 0。 | 10 | 11 | #### 返回值 12 | 13 | | 类型 | 描述 | 14 | | -------- | ---------------------------------------------------------------------------------------------- | 15 | | `number` | 音频轨道的音量级别。取值范围 [0, 1],1 代表理论最大音量。通常该值大于 0.6 代表用户在持续说话。 | 16 | 17 | #### 使用示例 18 | 19 | ```jsx 20 | import { useVolumeLevel, useLocalMicrophoneTrack } from "agora-rtc-react"; 21 | 22 | function App() { 23 | const audioTrack = useLocalMicrophoneTrack(); 24 | const volumeLevel = useVolumeLevel(audioTrack); 25 | 26 | return
    {volumeLevel}
    ; 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/assets/styles.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from "react"; 2 | import { useMemo } from "react"; 3 | 4 | export const VideoTrackWrapperStyle: CSSProperties = { 5 | position: "relative", 6 | width: "100%", 7 | height: "100%", 8 | overflow: "hidden", 9 | background: "#000", 10 | }; 11 | 12 | export const VideoTrackStyle: CSSProperties = { 13 | width: "100%", 14 | height: "100%", 15 | }; 16 | 17 | export const FloatBoxStyle: CSSProperties = { 18 | position: "absolute", 19 | top: 0, 20 | left: 0, 21 | width: "100%", 22 | height: "100%", 23 | overflow: "hidden", 24 | zIndex: 2, 25 | }; 26 | 27 | export const useMergedStyle = (s1?: CSSProperties, s2?: CSSProperties): CSSProperties => 28 | useMemo(() => ({ ...s1, ...s2 }), [s1, s2]); 29 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/components/UserCover.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, ReactNode } from "react"; 2 | 3 | import { FloatBoxStyle } from "../assets/styles"; 4 | 5 | const CoverBlurStyle: CSSProperties = { 6 | width: "100%", 7 | height: "100%", 8 | background: "#1a1e21 center/cover no-repeat", 9 | filter: "blur(16px) brightness(0.4)", 10 | }; 11 | 12 | const CoverImgStyle: CSSProperties = { 13 | position: "absolute", 14 | top: "50%", 15 | left: "50%", 16 | maxWidth: "50%", 17 | maxHeight: "50%", 18 | aspectRatio: "1", 19 | transform: "translate(-50%, -50%)", 20 | borderRadius: "50%", 21 | overflow: "hidden", 22 | objectFit: "cover", 23 | }; 24 | 25 | export interface UserCoverProps { 26 | /** 27 | * Cover image url or a custom render function. 28 | */ 29 | cover: string | (() => ReactNode); 30 | } 31 | 32 | /** 33 | * User Cover image with blur background 34 | */ 35 | export function UserCover({ cover }: UserCoverProps) { 36 | return ( 37 |
    38 | {typeof cover === "string" ? ( 39 | <> 40 |
    41 | 42 | 43 | ) : ( 44 | cover() 45 | )} 46 |
    47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./LocalAudioTrack"; 2 | export * from "./LocalVideoTrack"; 3 | export * from "./LocalUser"; 4 | export * from "./RemoteAudioTrack"; 5 | export * from "./RemoteVideoTrack"; 6 | export * from "./RemoteUser"; 7 | export * from "./TrackBoundary"; 8 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/error.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCError } from "agora-rtc-sdk-ng"; 2 | 3 | type printType = "log" | "warn" | "error" | "info"; 4 | 5 | interface IAgoraRTCReactError extends Error { 6 | readonly rtcMethod: string; 7 | readonly rtcError: IAgoraRTCError | string; 8 | log: (type: printType) => void; 9 | } 10 | 11 | export class AgoraRTCReactError extends Error implements IAgoraRTCReactError { 12 | public readonly rtcMethod: string; 13 | public readonly rtcError: IAgoraRTCError | string; 14 | public override readonly name: string = "AgoraRTCReactException"; 15 | 16 | public constructor(rtcMethod: string, rtcError: IAgoraRTCError | string) { 17 | if (typeof rtcError === "string") { 18 | super(rtcError); 19 | } else { 20 | super(rtcError.message); 21 | } 22 | this.rtcMethod = rtcMethod; 23 | this.rtcError = rtcError; 24 | } 25 | 26 | public log(type: printType) { 27 | console[type](this); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./events"; 2 | export * from "./useRTCClient"; 3 | export * from "./useRTCScreenShareClient"; 4 | export * from "./useConnectionState"; 5 | export * from "./useIsConnected"; 6 | export * from "./useCurrentUID"; 7 | export * from "./useJoin"; 8 | export * from "./useNetworkQuality"; 9 | export * from "./usePublish"; 10 | export * from "./useVolumeLevel"; 11 | export * from "./useLocalMicrophoneTrack"; 12 | export * from "./useLocalCameraTrack"; 13 | export * from "./useRemoteAudioTracks"; 14 | export * from "./useRemoteVideoTracks"; 15 | export * from "./useRemoteUserTrack"; 16 | export * from "./useRemoteUsers"; 17 | export * from "./useLocalScreenTrack"; 18 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/types.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient, IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng"; 2 | import type { ReactNode } from "react"; 3 | 4 | export interface massUserProps { 5 | user: IAgoraRTCRemoteUser; 6 | mediaType: "audio" | "video"; 7 | } 8 | 9 | export interface AgoraRTCProviderProps { 10 | readonly client: IAgoraRTCClient; 11 | readonly children?: ReactNode; 12 | } 13 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useConnectionState.ts: -------------------------------------------------------------------------------- 1 | import type { ConnectionState, IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { useRTCClient } from "../hooks/useRTCClient"; 5 | import { listen } from "../misc/listen"; 6 | import { joinDisposers, timeout } from "../misc/utils"; 7 | 8 | /** 9 | * Returns the detailed connection state of the SDK. 10 | * 11 | * @param client - Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. 12 | * @example 13 | * ```jsx 14 | * import { useConnectionState } from "agora-rtc-react"; 15 | * 16 | * function App() { 17 | * const connectionState = useConnectionState(); 18 | * 19 | * return
    {connectionState}
    ; 20 | * } 21 | * ``` 22 | */ 23 | export function useConnectionState(client?: IAgoraRTCClient | null): ConnectionState { 24 | const resolvedClient = useRTCClient(client); 25 | 26 | const [connectionState, setConnectionState] = useState( 27 | resolvedClient ? resolvedClient.connectionState : "DISCONNECTED", 28 | ); 29 | useEffect(() => { 30 | if (resolvedClient) { 31 | setConnectionState(resolvedClient.connectionState); 32 | let dispose: (() => void) | undefined; 33 | return joinDisposers([ 34 | listen(resolvedClient, "connection-state-change", state => { 35 | dispose?.(); 36 | if (state === "CONNECTED") { 37 | // RTC is really connected after a short delay 38 | dispose = timeout(() => setConnectionState(state), 0); 39 | } else { 40 | setConnectionState(state); 41 | } 42 | }), 43 | () => dispose?.(), 44 | ]); 45 | } else { 46 | setConnectionState("DISCONNECTED"); 47 | } 48 | }, [resolvedClient]); 49 | 50 | return connectionState; 51 | } 52 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useCurrentUID.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient, UID } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { useRTCClient } from "../hooks/useRTCClient"; 5 | import { listen } from "../misc/listen"; 6 | import { timeout } from "../misc/utils"; 7 | 8 | /** 9 | * Returns the current user ID. 10 | * 11 | * @param client - Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. 12 | * @example 13 | * ```jsx 14 | * import { useCurrentUID } from "agora-rtc-react"; 15 | * 16 | * function App() { 17 | * const uid = useCurrentUID(); 18 | * 19 | * return
    {uid}
    ; 20 | * } 21 | * ``` 22 | */ 23 | export function useCurrentUID(client?: IAgoraRTCClient | null): UID | undefined { 24 | const resolvedClient = useRTCClient(client); 25 | 26 | const [uid, setUID] = useState(resolvedClient?.uid); 27 | useEffect(() => { 28 | if (resolvedClient) { 29 | return listen(resolvedClient, "connection-state-change", state => { 30 | if (state === "CONNECTED") { 31 | // RTC is really connected after a short delay 32 | return timeout(() => setUID(resolvedClient.uid), 0); 33 | } else if (state === "DISCONNECTED") { 34 | setUID(void 0); 35 | } 36 | }); 37 | } 38 | }, [resolvedClient]); 39 | 40 | return uid; 41 | } 42 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useIsConnected.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { useRTCClient } from "../hooks/useRTCClient"; 5 | import { listen } from "../misc/listen"; 6 | import { joinDisposers, timeout } from "../misc/utils"; 7 | 8 | /** 9 | * Returns whether the SDK is connected to Agora's server. 10 | * 11 | * @param client - Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. 12 | * @example 13 | * ```jsx 14 | * import { useIsConnected } from "agora-rtc-react"; 15 | * 16 | * function App() { 17 | * const isConnected = useIsConnected(); 18 | * 19 | * return
    {isConnected}
    ; 20 | * } 21 | * ``` 22 | */ 23 | export function useIsConnected(client?: IAgoraRTCClient | null): boolean { 24 | const resolvedClient = useRTCClient(client); 25 | 26 | const [isConnected, setConnected] = useState( 27 | resolvedClient ? resolvedClient.connectionState === "CONNECTED" : false, 28 | ); 29 | useEffect(() => { 30 | if (resolvedClient) { 31 | setConnected(resolvedClient.connectionState === "CONNECTED"); 32 | let dispose: (() => void) | undefined; 33 | return joinDisposers([ 34 | listen(resolvedClient, "connection-state-change", state => { 35 | dispose?.(); 36 | // RTC is really connected after a short delay 37 | dispose = timeout(() => setConnected(state === "CONNECTED"), 0); 38 | }), 39 | () => dispose?.(), 40 | ]); 41 | } else { 42 | setConnected(false); 43 | } 44 | }, [resolvedClient]); 45 | 46 | return isConnected; 47 | } 48 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useNetworkQuality.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { useRTCClient } from "../hooks/useRTCClient"; 5 | import { listen } from "../misc/listen"; 6 | import type { NetworkQualityEx } from "../types"; 7 | 8 | const initQuality = (): NetworkQualityEx => ({ 9 | uplinkNetworkQuality: 0, 10 | downlinkNetworkQuality: 0, 11 | delay: 0, 12 | }); 13 | 14 | /** 15 | * Returns the network quality of the local user. 16 | * 17 | * @param client - Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. 18 | * @example 19 | * ```jsx 20 | * import { useNetworkQuality } from "agora-rtc-react"; 21 | * 22 | * function App() { 23 | * const networkQuality = useNetworkQuality(); 24 | * 25 | * return
    {networkQuality}
    ; 26 | * } 27 | * ``` 28 | */ 29 | export function useNetworkQuality(client?: IAgoraRTCClient | null): NetworkQualityEx { 30 | const resolvedClient = useRTCClient(client); 31 | 32 | const [networkQuality, setNetworkQuality] = useState(initQuality); 33 | useEffect(() => { 34 | if (resolvedClient) { 35 | return listen(resolvedClient, "network-quality", q => 36 | setNetworkQuality({ 37 | uplinkNetworkQuality: q.uplinkNetworkQuality, 38 | downlinkNetworkQuality: q.downlinkNetworkQuality, 39 | delay: resolvedClient.getRTCStats().RTT ?? 0, 40 | }), 41 | ); 42 | } else { 43 | setNetworkQuality(initQuality()); 44 | } 45 | }, [resolvedClient]); 46 | 47 | return networkQuality; 48 | } 49 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useRTCClient.tsx: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import { createContext, useContext } from "react"; 3 | 4 | import type { AgoraRTCProviderProps } from "./types"; 5 | 6 | const AgoraRTCContext = /* @__PURE__ */ createContext(null); 7 | 8 | export function AgoraRTCProvider({ client, children }: AgoraRTCProviderProps) { 9 | return {children}; 10 | } 11 | /** 12 | * @ignore 13 | */ 14 | function useOptionalRTCClient(client?: IAgoraRTCClient | null): IAgoraRTCClient | null { 15 | const clientFromContext = useContext(AgoraRTCContext); 16 | return client || clientFromContext; 17 | } 18 | 19 | /** 20 | * Returns the IAgoraRTCClient object. 21 | * 22 | * @param client - If provided, the passed `IAgoraRTCClient` object is returned. If not provided, the `IAgoraRTCClient` object obtained from the [parent component's context](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/AgoraRTCProvider.html) is returned. 23 | * @example 24 | * ```jsx 25 | * import { useRTCClient } from "agora-rtc-react"; 26 | * 27 | * function App() { 28 | * const client = useRTCClient(); 29 | * 30 | * return <>; 31 | * } 32 | * ``` 33 | */ 34 | export function useRTCClient(client?: IAgoraRTCClient | null): IAgoraRTCClient { 35 | const resolvedClient = useOptionalRTCClient(client); 36 | 37 | if (!resolvedClient) { 38 | throw new Error( 39 | "Agora RTC client not found. Should be wrapped in ", 40 | ); 41 | } 42 | 43 | return resolvedClient; 44 | } 45 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useRTCScreenShareClient.tsx: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import type { ReactNode } from "react"; 3 | import { createContext, useContext } from "react"; 4 | 5 | const AgoraRTCScreenShareContext = /* @__PURE__ */ createContext(null); 6 | 7 | export interface AgoraRTCScreenShareProviderProps { 8 | readonly client: IAgoraRTCClient; 9 | readonly children?: ReactNode; 10 | } 11 | 12 | export function AgoraRTCScreenShareProvider({ 13 | client, 14 | children, 15 | }: AgoraRTCScreenShareProviderProps) { 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | } 22 | 23 | export function useRTCScreenShareClient(client?: IAgoraRTCClient | null): IAgoraRTCClient | null { 24 | const clientFromContext = useContext(AgoraRTCScreenShareContext); 25 | return client || clientFromContext; 26 | } 27 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useRemoteUsers.ts: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient, IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { useRTCClient } from "../hooks/useRTCClient"; 5 | import { listen } from "../misc/listen"; 6 | import { joinDisposers } from "../misc/utils"; 7 | 8 | /** 9 | * This hook lets you retrieve the list of remote users. 10 | * The return value of this hook is updated in the following cases: 11 | * When a remote user joins or leaves the channel. 12 | * When the role of a remote user changes (for example, from broadcaster to audience). 13 | * When a remote user publishes or unpublishes the audio or video track. 14 | * 15 | * @param client - Created using the Web SDK's [`IAgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) method. 16 | * @example 17 | * ```jsx 18 | * import { useRemoteUsers } from "agora-rtc-react"; 19 | * 20 | * function App() { 21 | * const remoteUsers = useRemoteUsers(); 22 | * 23 | * return <>; 24 | * } 25 | * ``` 26 | */ 27 | export function useRemoteUsers(client?: IAgoraRTCClient | null): IAgoraRTCRemoteUser[] { 28 | const resolvedClient = useRTCClient(client); 29 | const [users, setUsers] = useState(resolvedClient ? resolvedClient.remoteUsers : []); 30 | 31 | useEffect(() => { 32 | if (resolvedClient) { 33 | // .slice(): make sure the array reference is updated 34 | const update = () => setUsers(resolvedClient.remoteUsers.slice()); 35 | return joinDisposers([ 36 | listen(resolvedClient, "user-joined", update), 37 | listen(resolvedClient, "user-left", update), 38 | listen(resolvedClient, "user-published", update), 39 | listen(resolvedClient, "user-unpublished", update), 40 | ]); 41 | } 42 | }, [resolvedClient]); 43 | 44 | return users; 45 | } 46 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/hooks/useVolumeLevel.ts: -------------------------------------------------------------------------------- 1 | import type { ILocalAudioTrack, IRemoteAudioTrack } from "agora-rtc-sdk-ng"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { interval } from "../misc/utils"; 5 | 6 | /** 7 | * Returns the volume level of an audio track at a frequency of once per second. 8 | * 9 | * @param audioTrack - The local or remote audio track. The local audio track can be created by calling [`useLocalMicrophoneTrack`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useLocalMicrophoneTrack.html). If undefined, the volume level is 0. 10 | * @example 11 | * ```jsx 12 | * import { useVolumeLevel, useLocalMicrophoneTrack } from "agora-rtc-react"; 13 | * 14 | * function App() { 15 | * const audioTrack = useLocalMicrophoneTrack(); 16 | * const volumeLevel = useVolumeLevel(audioTrack); 17 | * 18 | * return
    {volumeLevel}
    ; 19 | * } 20 | * ``` 21 | */ 22 | export function useVolumeLevel(audioTrack?: IRemoteAudioTrack | ILocalAudioTrack): number { 23 | const [volumeLevel, setVolumeLevel] = useState(0); 24 | 25 | useEffect(() => { 26 | if (audioTrack) { 27 | return interval(() => { 28 | setVolumeLevel(audioTrack.getVolumeLevel()); 29 | }, 1000); 30 | } 31 | }, [audioTrack]); 32 | 33 | return volumeLevel; 34 | } 35 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/index.ts: -------------------------------------------------------------------------------- 1 | import AgoraRTC from "agora-rtc-sdk-ng"; 2 | 3 | export * from "./hooks"; 4 | export * from "./components"; 5 | export * from "./rtc"; 6 | export * from "./error"; 7 | export * from "./types"; 8 | 9 | export default AgoraRTC; 10 | export * from "agora-rtc-sdk-ng"; 11 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/misc/store.ts: -------------------------------------------------------------------------------- 1 | import type { Disposer } from "./utils"; 2 | 3 | /** Callback to inform of a value updates. */ 4 | export type Subscriber = (value: T) => void; 5 | 6 | /** Start and stop notification callbacks. */ 7 | export type StartStopNotifier = (set: Subscriber) => Disposer | void; 8 | 9 | /** Readable interface for subscribing. */ 10 | export interface Readable { 11 | /** 12 | * Subscribe on value changes. 13 | * @param run subscription callback 14 | */ 15 | subscribe: (this: void, run: Subscriber) => Disposer; 16 | } 17 | 18 | export function readable(value: T, start: StartStopNotifier): Readable { 19 | let stop: Disposer | null | undefined; 20 | const subscribers = new Set>(); 21 | 22 | function set(newValue: T) { 23 | if (!Object.is(value, newValue)) { 24 | value = newValue; 25 | if (stop) { 26 | for (const subscriber of subscribers) { 27 | subscriber(value); 28 | } 29 | } 30 | } 31 | } 32 | 33 | function subscribe(run: Subscriber) { 34 | subscribers.add(run); 35 | if (subscribers.size === 1) { 36 | stop = start(set) || noop; 37 | } 38 | run(value); 39 | return () => { 40 | subscribers.delete(run); 41 | if (subscribers.size === 0 && stop) { 42 | stop(); 43 | stop = null; 44 | } 45 | }; 46 | } 47 | 48 | return { subscribe }; 49 | } 50 | 51 | function noop() { 52 | // noop 53 | } 54 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/rtc.ts: -------------------------------------------------------------------------------- 1 | import AgoraRTC from "agora-rtc-sdk-ng"; 2 | 3 | interface IAgoraRTCReact { 4 | readonly appType: number; 5 | } 6 | 7 | class AgoraRTCReact implements IAgoraRTCReact { 8 | readonly appType = 1001; 9 | 10 | public constructor() { 11 | AgoraRTC.setAppType(this.appType); 12 | } 13 | } 14 | 15 | new AgoraRTCReact(); 16 | 17 | export const VERSION = "2.4.0"; 18 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/LocalAudioTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeLocalAudioTrack } from "agora-rtc-sdk-ng-fake"; 3 | 4 | import type { LocalAudioTrackProps } from "../components"; 5 | import { LocalAudioTrack } from "../components"; 6 | 7 | const meta: Meta = { 8 | title: "Track/LocalAudioTrack", 9 | component: LocalAudioTrack, 10 | parameters: { 11 | layout: "fullscreen", 12 | }, 13 | argTypes: { 14 | track: { 15 | control: { 16 | type: null, 17 | }, 18 | }, 19 | }, 20 | render(args) { 21 | return ( 22 | 23 |

    An Example Local Audio Track

    24 |
    25 | ); 26 | }, 27 | }; 28 | 29 | export default meta; 30 | 31 | export const Enabled: StoryObj = { 32 | args: { 33 | track: FakeLocalAudioTrack.create(), 34 | play: true, 35 | }, 36 | }; 37 | 38 | export const EmptyTrack: StoryObj = { 39 | args: { 40 | play: true, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/LocalVideoTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeLocalVideoTrack } from "agora-rtc-sdk-ng-fake"; 3 | import { useState } from "react"; 4 | 5 | import type { LocalVideoTrackProps } from "../components"; 6 | import { LocalVideoTrack } from "../components"; 7 | 8 | const meta: Meta = { 9 | title: "Track/LocalVideoTrack", 10 | component: LocalVideoTrack, 11 | parameters: { 12 | layout: "fullscreen", 13 | }, 14 | argTypes: { 15 | track: { 16 | control: { 17 | type: null, 18 | }, 19 | }, 20 | }, 21 | render: function RenderLocalVideoTrack(args) { 22 | const [track] = useState(() => (args.track ? FakeLocalVideoTrack.create() : undefined)); 23 | return ; 24 | }, 25 | }; 26 | 27 | export default meta; 28 | 29 | export const Enabled: StoryObj = { 30 | args: { 31 | track: FakeLocalVideoTrack.create(), 32 | play: true, 33 | }, 34 | }; 35 | 36 | export const EmptyTrack: StoryObj = { 37 | args: { 38 | play: true, 39 | children:

    An Empty Local Video Track

    , 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/RemoteAudioTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeRemoteAudioTrack } from "agora-rtc-sdk-ng-fake"; 3 | 4 | import type { RemoteAudioTrackProps } from "../components"; 5 | import { RemoteAudioTrack } from "../components"; 6 | 7 | const meta: Meta = { 8 | title: "Track/RemoteAudioTrack", 9 | component: RemoteAudioTrack, 10 | parameters: { 11 | layout: "fullscreen", 12 | }, 13 | argTypes: { 14 | track: { 15 | control: { 16 | type: null, 17 | }, 18 | }, 19 | }, 20 | render(args) { 21 | return ( 22 | 23 |

    An Example Remote Audio Track

    24 |
    25 | ); 26 | }, 27 | }; 28 | 29 | export default meta; 30 | 31 | export const Enabled: StoryObj = { 32 | args: { 33 | track: FakeRemoteAudioTrack.create(), 34 | play: true, 35 | }, 36 | }; 37 | 38 | export const EmptyTrack: StoryObj = { 39 | args: { 40 | play: true, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/RemoteVideoTrack.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { FakeRemoteVideoTrack } from "agora-rtc-sdk-ng-fake"; 3 | import { useState } from "react"; 4 | 5 | import type { RemoteVideoTrackProps } from "../components"; 6 | import { RemoteVideoTrack } from "../components"; 7 | 8 | const meta: Meta = { 9 | title: "Track/RemoteVideoTrack", 10 | component: RemoteVideoTrack, 11 | parameters: { 12 | layout: "fullscreen", 13 | }, 14 | argTypes: { 15 | track: { 16 | control: { 17 | type: null, 18 | }, 19 | }, 20 | }, 21 | render: function RenderRemoteVideoTrack(args) { 22 | const [track] = useState(() => (args.track ? FakeRemoteVideoTrack.create() : undefined)); 23 | return ; 24 | }, 25 | }; 26 | 27 | export default meta; 28 | 29 | export const Enabled: StoryObj = { 30 | args: { 31 | track: FakeRemoteVideoTrack.create(), 32 | play: true, 33 | }, 34 | }; 35 | 36 | export const EmptyTrack: StoryObj = { 37 | args: { 38 | play: true, 39 | children:

    An Empty Remote Video Track

    , 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useAutoPlayAudioTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useAutoPlayAudioTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useAutoPlayVideoTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useAutoPlayVideoTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useClientEvent.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useClientEvent.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useConnectionState.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useConnectionState.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useCurrentUID.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useCurrentUID.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useIsConnected.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useIsConnected.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useJoin.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useJoin.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useLocalCameraTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useLocalCameraTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useLocalMicrophoneTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useLocalMicrophoneTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useLocalScreenTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useLocalScreenTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useNetworkQuality.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useNetworkQuality.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/usePublish.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/usePublish.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useRTCClient.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useRTCClient.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useRemoteAudioTracks.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useRemoteAudioTracks.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useRemoteUserTrack.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useRemoteUserTrack.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useRemoteUsers.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useRemoteUsers.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useRemoteVideoTracks.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useRemoteVideoTracks.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useTrackEvent.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useTrackEvent.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/stories/hooks/useVolumeLevel.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Readme from "../../../docs/hooks/useVolumeLevel.en-US.mdx?raw"; import { Meta, Markdown } from "@storybook/blocks"; {Readme} -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkQuality, UID } from "agora-rtc-sdk-ng"; 2 | 3 | export type FetchArgs = (() => Promise) | JoinOptions; 4 | 5 | /** 6 | * Parameters used to join a channel. 7 | */ 8 | export interface JoinOptions { 9 | /** 10 | * The App ID of your Agora project. 11 | */ 12 | appid: string; 13 | 14 | /** 15 | * The name of the channel to join. See [`IAgoraRTCClient.join`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#join) for details. 16 | */ 17 | channel: string; 18 | 19 | /** 20 | * The token used for authentication. If token-based authentication is enabled for your project, a valid token must be provided. If token-based authentication is not enabled, you can pass `null`. See [`IAgoraRTCClient.join`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#join) for details. 21 | */ 22 | token: string | null; 23 | 24 | /** 25 | * The user ID. If not provided, the Agora server assigns a number `uid` for you. See [`IAgoraRTCClient.join`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#join) for details. 26 | */ 27 | uid?: UID | null; 28 | } 29 | 30 | /** 31 | * The last-mile network quality. 32 | */ 33 | export interface NetworkQualityEx extends NetworkQuality { 34 | /** 35 | * The average Round-Trip Time (RTT) from the SDK to the Agora edge server, measured in milliseconds (ms). 36 | */ 37 | delay: number; 38 | } 39 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/component/RemoteVideoTrack.test.tsx: -------------------------------------------------------------------------------- 1 | import { composeStories } from "@storybook/react"; 2 | import { render } from "@testing-library/react"; 3 | import type { VideoPlayerConfig } from "agora-rtc-sdk-ng"; 4 | import { describe, expect, test, vi } from "vitest"; 5 | 6 | import { RemoteVideoTrack } from "../../src/components"; 7 | import * as fun from "../../src/components/TrackBoundary"; 8 | import * as stories from "../../src/stories/RemoteVideoTrack.stories"; 9 | const { Enabled, EmptyTrack } = composeStories(stories); 10 | 11 | vi.mock("../../src/components/TrackBoundary", () => ({ 12 | useAutoPlayVideoTrack: vi.fn(), 13 | })); 14 | 15 | describe("RemoteVideoTrack component", () => { 16 | test("renders without crashing", () => { 17 | const { container } = render(); 18 | expect(container).toBeInTheDocument(); 19 | vi.clearAllMocks(); 20 | }); 21 | 22 | test("config videoPlayerConfig on RemoteVideoTrack", () => { 23 | vi.spyOn(fun, "useAutoPlayVideoTrack"); 24 | const videoPlayerConfig: VideoPlayerConfig = { mirror: false, fit: "cover" }; 25 | render(); 26 | expect(fun.useAutoPlayVideoTrack).toBeCalledWith( 27 | undefined, 28 | true, 29 | videoPlayerConfig, 30 | expect.anything(), 31 | ); 32 | vi.clearAllMocks(); 33 | }); 34 | }); 35 | 36 | describe("RemoteVideoTrack component stories", () => { 37 | test("renders Enabled stories", () => { 38 | const { container } = render(); 39 | expect(container).toBeInTheDocument(); 40 | }); 41 | 42 | test("renders EmptyTrack stories", () => { 43 | const { getByText } = render(); 44 | expect(getByText(/An Empty Remote Video Track/i)).toBeInTheDocument(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useConnectionState.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from "@testing-library/react"; 2 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 3 | import { FakeRTCClient, dispatchRTCEvent } from "agora-rtc-sdk-ng-fake"; 4 | import { expect, test } from "vitest"; 5 | 6 | import { useConnectionState } from "../../src/hooks/index"; 7 | 8 | const setUp = (client?: IAgoraRTCClient | null) => renderHook(() => useConnectionState(client)); 9 | 10 | describe("useConnectionState", () => { 11 | test("should return client connection state", () => { 12 | const client = FakeRTCClient.create(); 13 | const { result } = setUp(client); 14 | expect(result.current).toBe(client.connectionState); 15 | }); 16 | 17 | test("should update state on connection-state-change event", () => { 18 | const client = FakeRTCClient.create(); 19 | const { result } = setUp(client); 20 | expect(result.current).toBe(client.connectionState); 21 | act(() => { 22 | dispatchRTCEvent(client, "connection-state-change", "CONNECTING"); 23 | }); 24 | expect(result.current).toBe("CONNECTING"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useCurrentUID.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook, waitFor } from "@testing-library/react"; 2 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 3 | import { FakeRTCClient, dispatchRTCEvent } from "agora-rtc-sdk-ng-fake"; 4 | import { expect } from "vitest"; 5 | 6 | import { useCurrentUID } from "../../src/hooks/index"; 7 | const setUp = (client?: IAgoraRTCClient | null) => renderHook(() => useCurrentUID(client)); 8 | 9 | describe("useCurrentUID", () => { 10 | test("returns the client's uid when connected", () => { 11 | const client = FakeRTCClient.create(); 12 | const { result } = setUp(client); 13 | expect(result.current).toBe(client.uid); 14 | }); 15 | 16 | test("returns 0 when disConnected", async () => { 17 | const client = FakeRTCClient.create(); 18 | const { result } = setUp(client); 19 | act(() => { 20 | dispatchRTCEvent(client, "connection-state-change", "DISCONNECTED"); 21 | }); 22 | await waitFor(() => { 23 | expect(result.current).toBe(void 0); 24 | }); 25 | }); 26 | 27 | test("returns uid when CONNECTED", async () => { 28 | const client = FakeRTCClient.create(); 29 | (client.uid as IAgoraRTCClient["uid"]) = 33; 30 | (client.connectionState as IAgoraRTCClient["connectionState"]) = "CONNECTED"; 31 | const { result } = setUp(client); 32 | await waitFor(() => { 33 | expect(result.current).toBe(33); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useIsConnected.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from "@testing-library/react"; 2 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 3 | import { FakeRTCClient } from "agora-rtc-sdk-ng-fake"; 4 | import { expect } from "vitest"; 5 | 6 | import { useIsConnected } from "../../src/hooks/useIsConnected"; 7 | const setUp = (client?: IAgoraRTCClient | null) => renderHook(() => useIsConnected(client)); 8 | 9 | describe("useIsConnected", () => { 10 | test("returns false when client is not connected", () => { 11 | const client = FakeRTCClient.create(); 12 | (client.connectionState as IAgoraRTCClient["connectionState"]) = "DISCONNECTED"; 13 | const { result } = setUp(client); 14 | expect(result.current).toBe(false); 15 | }); 16 | 17 | test("returns true when client is connected", () => { 18 | const client = FakeRTCClient.create(); 19 | (client.connectionState as IAgoraRTCClient["connectionState"]) = "CONNECTED"; 20 | const { result } = setUp(client); 21 | expect(result.current).toBe(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useNetworkQuality.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from "@testing-library/react"; 2 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 3 | import { FakeRTCClient, dispatchRTCEvent } from "agora-rtc-sdk-ng-fake"; 4 | import { expect, vi } from "vitest"; 5 | 6 | import { useNetworkQuality } from "../../src/hooks/index"; 7 | import type { NetworkQualityEx } from "../../src/types"; 8 | const setUp = (client?: IAgoraRTCClient | null) => renderHook(() => useNetworkQuality(client)); 9 | 10 | describe("useNetworkQuality", () => { 11 | test("default value", () => { 12 | const initQuality = (): NetworkQualityEx => ({ 13 | uplinkNetworkQuality: 0, 14 | downlinkNetworkQuality: 0, 15 | delay: 0, 16 | }); 17 | 18 | const client = FakeRTCClient.create(); 19 | const { result } = setUp(client); 20 | expect(result.current).toStrictEqual(initQuality()); 21 | client.getRTCStats = vi.fn().mockReturnValue({ RTT: 3 }); 22 | const newQuality = { 23 | uplinkNetworkQuality: 1, 24 | downlinkNetworkQuality: 2, 25 | }; 26 | 27 | act(() => { 28 | dispatchRTCEvent(client, "network-quality", newQuality); 29 | }); 30 | expect(result.current).toStrictEqual({ 31 | uplinkNetworkQuality: 1, 32 | downlinkNetworkQuality: 2, 33 | delay: 3, 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useRTCClient.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from "@testing-library/react"; 2 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 3 | import { FakeRTCClient } from "agora-rtc-sdk-ng-fake"; 4 | import { expect } from "vitest"; 5 | 6 | import { useRTCClient } from "../../src/hooks/index"; 7 | const setUp = (client?: IAgoraRTCClient | null) => renderHook(() => useRTCClient(client)); 8 | 9 | describe("useRTCClient", () => { 10 | test("should throw an error if no client is provided", () => { 11 | expect(() => setUp(null)).toThrow(/^Agora RTC client not found/); 12 | }); 13 | 14 | test("should return the provided client", () => { 15 | const client = FakeRTCClient.create(); 16 | const { result } = setUp(client); 17 | expect(result.current).toBe(client); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/test/hook/useVolumeLevel.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, waitFor } from "@testing-library/react"; 2 | import type { ILocalAudioTrack, IRemoteAudioTrack } from "agora-rtc-sdk-ng"; 3 | import { FakeLocalAudioTrack } from "agora-rtc-sdk-ng-fake"; 4 | import { act } from "react-dom/test-utils"; 5 | import { expect, vi } from "vitest"; 6 | 7 | import { useVolumeLevel } from "../../src/hooks/index"; 8 | const setUp = (audioTrack?: IRemoteAudioTrack | ILocalAudioTrack) => 9 | renderHook(() => useVolumeLevel(audioTrack)); 10 | 11 | describe("useVolumeLevel", () => { 12 | test("should return volumeLevel by getVolumeLevel", async () => { 13 | const audioTrack = FakeLocalAudioTrack.create(); 14 | vi.spyOn(audioTrack, "getVolumeLevel"); 15 | vi.useFakeTimers(); 16 | const { result } = setUp(audioTrack); 17 | act(() => { 18 | vi.advanceTimersByTime(1000); 19 | }); 20 | await waitFor(() => { 21 | expect(audioTrack.getVolumeLevel).toHaveBeenCalled(); 22 | expect(result.current).toEqual(audioTrack.getVolumeLevel()); 23 | }); 24 | jest.useRealTimers(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "noFallthroughCasesInSwitch": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "noUnusedParameters": true, 8 | "noImplicitOverride": true, 9 | "module": "ESNext", 10 | "target": "ESNext", 11 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 12 | "moduleResolution": "Node", 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "skipLibCheck": true, 16 | "jsx": "react-jsx" 17 | }, 18 | "include": ["src", "test", "../shared/test/setup.tsx", "../shared/test/setup"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | import setGlobals from "../../scripts/tsup/set-globals"; 4 | 5 | import pkg from "./package.json"; 6 | 7 | const banner = ` 8 | /** 9 | * @license ${pkg.name} 10 | * @version ${pkg.version} 11 | * 12 | * Copyright (c) Agora, Inc. 13 | * 14 | * This source code is licensed under the MIT license. 15 | */ 16 | `; 17 | 18 | export default defineConfig([ 19 | { 20 | entry: { 21 | [pkg.name]: "src/index.ts", 22 | }, 23 | banner: () => { 24 | return { 25 | js: banner, 26 | }; 27 | }, 28 | format: ["cjs", "esm"], 29 | splitting: false, 30 | sourcemap: false, 31 | clean: true, 32 | treeshake: true, 33 | dts: { 34 | resolve: ["agora-rtc-sdk-ng"], 35 | }, 36 | minify: true, 37 | }, 38 | { 39 | entry: { 40 | [pkg.name]: "src/index.ts", 41 | }, 42 | banner: () => { 43 | return { 44 | js: banner, 45 | }; 46 | }, 47 | outExtension: () => { 48 | return { 49 | js: `.iife.js`, 50 | }; 51 | }, 52 | format: ["iife"], 53 | sourcemap: false, 54 | splitting: false, 55 | clean: true, 56 | minify: true, 57 | external: Object.keys(pkg.peerDependencies), 58 | define: { 59 | "process.env.NODE_ENV": JSON.stringify("production"), 60 | }, 61 | globalName: "AgoraRTC", 62 | esbuildPlugins: [ 63 | setGlobals({ 64 | "react": "React", 65 | "react-dom": "ReactDOM", 66 | }), 67 | ], 68 | platform: "browser", 69 | }, 70 | ]); 71 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "../../typedoc", 3 | "name": "Video SDK for React", 4 | "includeVersion": true, 5 | "hideGenerator": true, 6 | "githubPages": false 7 | } 8 | -------------------------------------------------------------------------------- /packages/agora-rtc-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /// 4 | 5 | import react from "@vitejs/plugin-react"; 6 | import { defineConfig } from "vite"; 7 | 8 | export default defineConfig({ 9 | plugins: [react()], 10 | test: { 11 | onConsoleLog(log) { 12 | if (log.includes("Agora RTC client not found")) return false; 13 | if (log.includes("Agora-SDK [DEBUG]: ")) return false; 14 | if (log.includes("Agora-SDK [WARNING]: ")) return false; 15 | if (log.includes("Agora-SDK [ERROR]: ")) return false; 16 | if (log.includes("Agora-SDK [INFO]: ")) return false; 17 | if (log.includes("Agora-RTC-REACT [ERROR_TEST_MSG]")) return false; 18 | }, 19 | environment: "jsdom", 20 | globals: true, 21 | coverage: { 22 | provider: "v8", 23 | reporter: ["text", "json", "html", "lcov", "json-summary"], 24 | exclude: [ 25 | "src/misc/*", 26 | "src/stories/*", 27 | "src/assets/*", 28 | "src/*/index.ts", 29 | "src/hooks/tools.ts", 30 | "test/**", 31 | "src/rtc.ts", 32 | ], 33 | }, 34 | exclude: ["**/node_modules/**"], 35 | deps: { 36 | inline: ["vitest-canvas-mock"], 37 | }, 38 | setupFiles: ["../shared/test/setup.tsx"], 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /packages/shared/assets/storybook/global.scss: -------------------------------------------------------------------------------- 1 | .css-1kwwth4 { 2 | color: inherit !important; 3 | } 4 | -------------------------------------------------------------------------------- /packages/shared/test/setup.tsx: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | import type { ReactNode } from "react"; 3 | import { vi } from "vitest"; 4 | import "vitest-canvas-mock"; 5 | 6 | /** 7 | * started agora-rtc-sdk-ng@17.0.0, need mock RTCPeerConnection. 8 | * RTCPeerConnection does not implement global 9 | */ 10 | // @ts-expect-error type 11 | global.RTCPeerConnection = vi.fn(); 12 | 13 | /** 14 | * JSDOM does not implement global "HTMLMediaElement.prototype.play" function 15 | */ 16 | HTMLMediaElement.prototype.play = vi.fn().mockReturnValue(Promise.resolve()); 17 | HTMLMediaElement.prototype.pause = vi.fn().mockReturnValue(Promise.resolve()); 18 | 19 | /** 20 | * navigator does not implement global "mediaDevices.prototype.getUserMedia" function 21 | * navigator does not implement global "mediaDevices.prototype.enumerateDevices" function 22 | * 23 | */ 24 | const mockPromise = vi.fn(async () => { 25 | return new Promise(resolve => { 26 | resolve(); 27 | }); 28 | }); 29 | Object.defineProperty(global.navigator, "mediaDevices", { 30 | value: { 31 | getUserMedia: mockPromise, 32 | enumerateDevices: mockPromise, 33 | }, 34 | }); 35 | 36 | export interface Props { 37 | children: ReactNode; 38 | } 39 | 40 | window.MediaStreamTrack = jest.fn(); 41 | window.RTCIceCandidate = jest.fn(); 42 | -------------------------------------------------------------------------------- /packages/shared/test/setup/agora.tsx: -------------------------------------------------------------------------------- 1 | import AgoraRTC from "agora-rtc-sdk-ng"; 2 | 3 | AgoraRTC.setLogLevel(4); 4 | 5 | export const errorMessage = "Agora-RTC-REACT [ERROR_TEST_MSG]"; 6 | -------------------------------------------------------------------------------- /packages/shared/test/setup/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import type { IAgoraRTCClient } from "agora-rtc-sdk-ng"; 2 | import type { ReactNode } from "react"; 3 | import React from "react"; 4 | 5 | import { AgoraRTCProvider } from "../../../agora-rtc-react/src/hooks"; 6 | import type { Props } from "../setup"; 7 | 8 | export const createWrapper = 9 | (client: IAgoraRTCClient): React.FC => 10 | ({ children }: { children: ReactNode }) => 11 | {children}; 12 | -------------------------------------------------------------------------------- /patches/seedrandom@3.0.5.patch: -------------------------------------------------------------------------------- 1 | diff --git a/seedrandom.js b/seedrandom.js 2 | index 12d7ee1ddd93e8708629953a7ef416f64cc4be75..63439c59165c5526961eeb9a29b414c2182d118c 100644 3 | --- a/seedrandom.js 4 | +++ b/seedrandom.js 5 | @@ -232,9 +232,10 @@ mixkey(math.random(), pool); 6 | if ((typeof module) == 'object' && module.exports) { 7 | module.exports = seedrandom; 8 | // When in node.js, try using crypto package for autoseeding. 9 | - try { 10 | - nodecrypto = require('crypto'); 11 | - } catch (ex) {} 12 | + // PATCH: remove nodecrypto 13 | + // try { 14 | + // nodecrypto = require('crypto'); 15 | + // } catch (ex) {} 16 | } else if ((typeof define) == 'function' && define.amd) { 17 | define(function() { return seedrandom; }); 18 | } else { -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | -------------------------------------------------------------------------------- /scripts/const.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export const docType = ["components", "hooks", "data-types"]; 4 | export const languages = ["", "-en"]; 5 | export const languagesFormat = [".zh-CN", ".en-US"]; 6 | 7 | export const packagePath = path.join(__dirname, "..", "packages", "agora-rtc-react"); 8 | export const docsPath = path.join(packagePath, "docs"); 9 | export const urlPrefix = [ 10 | "https://doc.shengwang.cn/api-ref/rtc/react/react-sdk/", 11 | "https://api-ref.agora.io/en/video-sdk/reactjs/2.x/", 12 | ]; 13 | 14 | export const storiesPath = path.join(packagePath, "src", "stories"); 15 | export const hooksPath = path.join(packagePath, "src", "hooks"); 16 | export const componentsPath = path.join(packagePath, "src", "components"); 17 | export const dataTypesPathList = [ 18 | path.join(packagePath, "src", "types.ts"), 19 | path.join(packagePath, "src", "rtc.ts"), 20 | path.join(packagePath, "src", "error.ts"), 21 | ]; 22 | -------------------------------------------------------------------------------- /scripts/copy-docs.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | const docsPath = path.join(__dirname, "..", "docs"); 5 | 6 | if (fs.existsSync(docsPath)) { 7 | fs.rmSync(docsPath, { recursive: true, force: true }); 8 | } else { 9 | fs.mkdirSync(docsPath); 10 | } 11 | 12 | const storybookPath = path.join(__dirname, "..", "packages", "agora-rtc-react", "storybook-static"); 13 | if (fs.existsSync(storybookPath)) { 14 | fs.cpSync(storybookPath, docsPath, { recursive: true }); 15 | } 16 | 17 | for (const exampleName of fs.readdirSync("examples")) { 18 | if (exampleName.startsWith(".")) continue; 19 | 20 | const examplePath = path.join("examples", exampleName, "dist"); 21 | if (fs.existsSync(examplePath) && fs.lstatSync(examplePath).isDirectory()) { 22 | fs.cpSync(examplePath, path.join(docsPath, exampleName), { recursive: true }); 23 | } 24 | } 25 | 26 | // copy typedoc 27 | const typedocPath = path.join(__dirname, "..", "typedoc"); 28 | fs.cpSync(typedocPath, path.join(docsPath, "api-ref"), { recursive: true }); 29 | -------------------------------------------------------------------------------- /scripts/docs/generate-storybook-mdx.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | import { docType, docsPath, languagesFormat, storiesPath } from "../const"; 5 | import { emptyDirectory } from "../utils"; 6 | 7 | for (let j = 0; j < docType.length; j++) { 8 | emptyDirectory(`${storiesPath}/${docType[j]}`); 9 | } 10 | 11 | const copyDir = (sourceDir, targetDir) => { 12 | fs.readdir(sourceDir, (err, files) => { 13 | if (err) throw err; 14 | files.forEach(file => { 15 | const sourcePath = path.join(sourceDir, file); 16 | const targetPath = path.join(targetDir, file); 17 | fs.stat(sourcePath, (err, stats) => { 18 | if (err) throw err; 19 | if (stats.isDirectory()) { 20 | // only copy hooks 21 | if (path.basename(sourcePath) !== docType[1]) { 22 | return; 23 | } 24 | fs.stat(targetPath, err => { 25 | if (err) throw err; 26 | copyDir(sourcePath, targetPath); 27 | }); 28 | } else { 29 | //only copy .en-US.md 30 | if (file.indexOf(languagesFormat[1]) === -1) { 31 | return; 32 | } 33 | const docType = path.basename(path.dirname(targetPath)); 34 | const prependContent = `import Readme from "../../../docs/${docType}/${file}?raw";\r\rimport { Meta, Markdown } from "@storybook/blocks";\r\r\r\r{Readme}\r`; 38 | fs.writeFile(targetPath, prependContent, err => { 39 | if (err) throw err; 40 | console.log(`${sourcePath} copied to ${targetPath}`); 41 | }); 42 | } 43 | }); 44 | }); 45 | }); 46 | }; 47 | 48 | copyDir(docsPath, storiesPath); 49 | -------------------------------------------------------------------------------- /scripts/docs/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | export function tableToJson(table) { 5 | if (!table) { 6 | return []; 7 | } 8 | const data: HTMLTableCellElement[][] = []; 9 | for (let i = 1; i < table.rows.length; i++) { 10 | const tableRow: HTMLTableRowElement = table.rows[i]; 11 | const rowData: HTMLTableCellElement[] = []; 12 | for (let j = 0; j < tableRow.cells.length; j++) { 13 | rowData.push(tableRow.cells[j]); 14 | } 15 | data.push(rowData); 16 | } 17 | return data; 18 | } 19 | 20 | export async function readDirRecursively(dir, handler) { 21 | const files = fs.readdirSync(dir); 22 | files.forEach(async file => { 23 | const filePath = path.join(dir, file); 24 | const stat = fs.statSync(filePath); 25 | 26 | if (stat.isDirectory()) { 27 | readDirRecursively(filePath, handler); 28 | } else { 29 | handler && handler(filePath); 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /scripts/publishCN/common.sh: -------------------------------------------------------------------------------- 1 | # common.sh 2 | #!/bin/bash 3 | 4 | old_package_name=agora-rtc-react 5 | new_package_name=shengwang-rtc-react 6 | 7 | old_web_sdk_rtc=agora-rtc-sdk-ng 8 | new_web_sdk_rtc=shengwang-rtc-sdk-ng 9 | 10 | 11 | -------------------------------------------------------------------------------- /scripts/publishCN/rewrite-dep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | MY_PATH=$(realpath $(dirname "$0")) 4 | PROJECT_ROOT=$(realpath ${MY_PATH}/../..) 5 | . ${PROJECT_ROOT}/scripts/publishCN/common.sh 6 | 7 | change_file=${PROJECT_ROOT}/package.json 8 | sed "s/${old_package_name}/${new_package_name}/g" ${change_file} > tmp && mv tmp ${change_file} 9 | echo "${change_file} rewritten successfully" 10 | 11 | change_file=${PROJECT_ROOT}/packages/agora-rtc-react/package.json 12 | sed "s/${old_package_name}/${new_package_name}/g" ${change_file} > tmp && mv tmp ${change_file} 13 | echo "${change_file} rewritten successfully" 14 | 15 | change_file=${PROJECT_ROOT}/packages/agora-rtc-react-ui/package.json 16 | sed "s/${old_package_name}/${new_package_name}/g" ${change_file} > tmp && mv tmp ${change_file} 17 | echo "${change_file} rewritten successfully" 18 | -------------------------------------------------------------------------------- /scripts/publishCN/rewrite-example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | MY_PATH=$(realpath $(dirname "$0")) 4 | PROJECT_ROOT=$(realpath ${MY_PATH}/../..) 5 | . ${PROJECT_ROOT}/scripts/publishCN/common.sh 6 | 7 | change_dir="${PROJECT_ROOT}/examples/basic" 8 | 9 | find "$change_dir" -type f | while read -r file; do 10 | if [[ "$file" == *".ts" || "$file" == *".tsx" ]]; then 11 | sed -i.bak "s/${old_package_name}/${new_package_name}/g" "$file" 12 | echo "Replaced in $file" 13 | fi 14 | done 15 | 16 | find "$change_dir" -name "*.bak" -type f -delete 17 | 18 | echo "All replacements completed successfully, and backup files have been deleted." 19 | 20 | change_file=${PROJECT_ROOT}/examples/basic/package.json 21 | sed "s/${old_package_name}/${new_package_name}/g" ${change_file} > tmp && mv tmp ${change_file} 22 | echo "${change_file} rewritten successfully" 23 | -------------------------------------------------------------------------------- /scripts/release/clean.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | 3 | const args = process.argv.slice(2); 4 | 5 | for (let i = 0; i < args.length; i++) { 6 | if (args[i].startsWith("-")) { 7 | args.splice(args.indexOf(args[i]), 1); 8 | i--; 9 | } 10 | } 11 | 12 | if (args.length < 2 || !args[0].startsWith("source:") || !args[1].includes("@")) { 13 | console.log("Usage: node clean.ts source:@"); 14 | process.exit(1); 15 | } 16 | 17 | const source = args[0].substring(7); 18 | const output = args[1].substring(args[1].lastIndexOf("@") + 1); 19 | 20 | fs.readFile(source, "utf8", (err, data) => { 21 | if (err) throw err; 22 | 23 | const pkg = JSON.parse(data); 24 | 25 | delete pkg.scripts; 26 | delete pkg.devDependencies; 27 | delete pkg["release-it"]; 28 | delete pkg.source; 29 | delete pkg["publish-config"]; 30 | pkg["main"] = `dist/${pkg.name}.js`; 31 | pkg["types"] = `dist/${pkg.name}.d.ts`; 32 | pkg["module"] = `dist/${pkg.name}.mjs`; 33 | 34 | fs.writeFile(output, JSON.stringify(pkg), "utf8", err => { 35 | if (err) throw err; 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/release/update-version.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | const args = process.argv.slice(2); 5 | 6 | for (let i = 0; i < args.length; i++) { 7 | if (args[i].startsWith("-")) { 8 | args.splice(args.indexOf(args[i]), 1); 9 | i--; 10 | } 11 | } 12 | 13 | if (args.length < 2 || !args[0].startsWith("target:") || !args[1].includes("@")) { 14 | console.log("Usage: node update-version.ts target:@"); 15 | process.exit(1); 16 | } 17 | 18 | const targetName = args[0].substring(7); 19 | const newVersion = args[1].substring(args[1].lastIndexOf("@") + 1); 20 | 21 | const targetPath = path.join(__dirname, "..", "..", "packages", targetName, "src", "rtc.ts"); 22 | 23 | fs.readFile(targetPath, "utf8", (err, data) => { 24 | if (err) { 25 | console.error(err); 26 | return; 27 | } 28 | 29 | const updatedData = data.replace( 30 | /export const VERSION = ".+"/, 31 | `export const VERSION = "${newVersion}"`, 32 | ); 33 | 34 | fs.writeFile(targetPath, updatedData, err => { 35 | if (err) { 36 | console.error(err); 37 | return; 38 | } 39 | console.log(`${targetPath} updated with version ${newVersion}`); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /scripts/tsup/set-globals.ts: -------------------------------------------------------------------------------- 1 | const setGlobals = ( 2 | globals: { 3 | [key: string]: string; 4 | } = {}, 5 | ) => { 6 | return { 7 | name: "globals", 8 | setup({ onResolve, onLoad }) { 9 | onResolve({ filter: /^[^.]/ }, args => { 10 | if (args.path in globals) { 11 | return { path: args.path, namespace: "globals" }; 12 | } 13 | }); 14 | 15 | onLoad({ filter: /(?:)/, namespace: "globals" }, args => { 16 | return { contents: `module.exports = ${globals[args.path]}` }; 17 | }); 18 | }, 19 | }; 20 | }; 21 | 22 | export default setGlobals; 23 | -------------------------------------------------------------------------------- /scripts/upgrade-deps.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | const args = process.argv.slice(2); 5 | 6 | for (let i = 0; i < args.length; i++) { 7 | if (args[i].startsWith("-")) { 8 | args.splice(args.indexOf(args[i]), 1); 9 | i--; 10 | } 11 | } 12 | 13 | if (args.length < 2 || !args[0].startsWith("dep:") || !args[1].includes("@")) { 14 | console.log("Usage: node update-dep-version.js dep:@"); 15 | process.exit(1); 16 | } 17 | 18 | const depName = args[0].substring(4); 19 | const newVersion = args[1].substring(args[1].lastIndexOf("@") + 1); 20 | 21 | const packageJsonPath = path.join(process.cwd(), "package.json"); 22 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); 23 | 24 | if (packageJson.peerDependencies && packageJson.peerDependencies[depName]) { 25 | packageJson.peerDependencies[depName] = `>=${newVersion}`; 26 | console.log(`PeerDependency ${depName} version updated to >=${newVersion} in ${packageJsonPath}`); 27 | } 28 | 29 | if (packageJson.dependencies && packageJson.dependencies[depName]) { 30 | packageJson.dependencies[depName] = `${newVersion}`; 31 | console.log(`Dependency ${depName} version updated to ${newVersion} in ${packageJsonPath}`); 32 | } 33 | 34 | if (packageJson.devDependencies && packageJson.devDependencies[depName]) { 35 | packageJson.devDependencies[depName] = `${newVersion}`; 36 | console.log(`DevDependency ${depName} version updated to ${newVersion} in ${packageJsonPath}`); 37 | } 38 | 39 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n"); 40 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | 3 | export function emptyDirectory(path) { 4 | if (fs.existsSync(path)) { 5 | fs.readdirSync(path).forEach(function (file) { 6 | const curPath = path + "/" + file; 7 | if (fs.lstatSync(curPath).isDirectory()) { 8 | emptyDirectory(curPath); 9 | fs.rmdirSync(curPath); 10 | } else { 11 | fs.unlinkSync(curPath); 12 | } 13 | }); 14 | } else { 15 | fs.mkdirSync(path); 16 | } 17 | } 18 | --------------------------------------------------------------------------------