├── .gitignore ├── ExtensionDemo ├── .gitignore ├── README.md ├── babel.config.json ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── audio-extension.ts │ ├── index.ts │ └── video-extension.ts ├── tsconfig.json └── webpack.config.js ├── README.cn.md ├── README.md ├── package.json ├── scripts ├── pure.js └── server.js ├── src ├── CHANGELOG.md ├── assets │ ├── agora-logo-en.png │ ├── agora-logo-zh.png │ ├── bootstrap.bundle.min.js │ ├── bootstrap.min.css │ ├── favicon.ico │ ├── github.png │ └── jquery-3.4.1.min.js ├── common │ ├── common.css │ ├── constant.js │ ├── left-menu.js │ └── utils.js ├── example │ ├── advanced │ │ ├── adjustVideoProfile │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── audioEffects │ │ │ ├── HeroicAdventure.mp3 │ │ │ ├── audio.mp3 │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── customVideoSource │ │ │ ├── assets │ │ │ │ └── sample.mp4 │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── displayCallStats │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── geoFencing │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── joinMultipleChannel │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── screenshot │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── selfCapturing │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── selfRendering │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── shareTheScreen │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── testMediaDevices │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── jquery.jsonview.min.css │ │ │ └── jquery.jsonview.min.js │ │ └── vadExtention │ │ │ ├── agora-extension-vad │ │ │ ├── README.md │ │ │ ├── index.d.ts │ │ │ ├── index.esm.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── wasm │ │ │ │ ├── web-vad.wasm │ │ │ │ └── web-vad_simd.wasm │ │ │ ├── chart.js │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.js │ ├── basic │ │ ├── basicLive │ │ │ ├── assets │ │ │ │ └── doubt.png │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── basicVideoCall │ │ │ ├── index.html │ │ │ └── index.js │ │ └── basicVoiceCall │ │ │ ├── index.html │ │ │ └── index.js │ ├── extension │ │ ├── aiDenoiser │ │ │ ├── agora-extension-ai-denoiser │ │ │ │ ├── README.md │ │ │ │ ├── external │ │ │ │ │ ├── denoiser-wasm-simd.wasm │ │ │ │ │ └── denoiser-wasm.wasm │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.esm.js │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── beauty │ │ │ ├── agora-extension-beauty.js │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── spatialAudio │ │ │ ├── audio_spatializer.wasm │ │ │ ├── index.esm.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── resources │ │ │ │ ├── 1.mp3 │ │ │ │ ├── 2.mp3 │ │ │ │ └── 3.mp3 │ │ │ └── spatial-audio-worker.js │ │ ├── superClarity │ │ │ ├── agora-extension-pvc.js │ │ │ ├── agora-extension-super-clarity.js │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── vad │ │ │ ├── agora-extension-vad │ │ │ │ ├── README.md │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.esm.js │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ └── wasm │ │ │ │ │ ├── web-vad.wasm │ │ │ │ │ └── web-vad_simd.wasm │ │ │ ├── assets │ │ │ │ └── chart.js │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── videoCompositor │ │ │ ├── agora-extension-video-compositor │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── agora-extension-video-compositor.js │ │ │ │ ├── index.d.ts │ │ │ │ ├── index_cn.d.ts │ │ │ │ ├── index_en.d.ts │ │ │ │ └── package.json │ │ │ ├── assets │ │ │ │ ├── city.jpg │ │ │ │ └── space.jpg │ │ │ ├── index.html │ │ │ └── index.js │ │ └── virtualBackground │ │ │ ├── agora-extension-virtual-background │ │ │ ├── agora-extension-virtual-background.js │ │ │ ├── index.d.ts │ │ │ ├── index_cn.d.ts │ │ │ ├── index_en.d.ts │ │ │ ├── package.json │ │ │ └── wasms │ │ │ │ └── agora-wasm.wasm │ │ │ ├── index.html │ │ │ └── index.js │ ├── framework │ │ ├── react │ │ │ ├── assets │ │ │ │ ├── babel.min.js │ │ │ │ ├── react-dom.production.min.js │ │ │ │ └── react.production.min.js │ │ │ ├── index.html │ │ │ └── index.js │ │ └── vue │ │ │ ├── assets │ │ │ └── vue.min.js │ │ │ ├── index.html │ │ │ └── index.js │ ├── others │ │ ├── dualStream │ │ │ ├── index.html │ │ │ └── index.js │ │ └── pushStreamToCDN │ │ │ ├── index.html │ │ │ └── index.js │ └── quickStart │ │ └── videoAndVoiceCalling │ │ ├── index.html │ │ └── index.js ├── i18n │ ├── en │ │ └── index.json │ ├── language.js │ └── zh-CN │ │ └── index.json ├── index.html └── index.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .vercel 7 | -------------------------------------------------------------------------------- /ExtensionDemo/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .code/ -------------------------------------------------------------------------------- /ExtensionDemo/README.md: -------------------------------------------------------------------------------- 1 | # Agora-RTE-Extension Demo 2 | 3 | A simple demo for how to create video and audio extension for Agora Web SDK NG. 4 | 5 | Detailed documentation [here](https://docs.agora.io/en/extension_vendor/plugin_web_ng?platform=Web). 6 | 7 | ## Install Dependencies 8 | 9 | ```shell 10 | npm install 11 | ``` 12 | 13 | ## Build 14 | 15 | ```shell 16 | npm run build 17 | ``` 18 | 19 | ## Run the demo 20 | 21 | ```shell 22 | npm run demo 23 | ``` 24 | -------------------------------------------------------------------------------- /ExtensionDemo/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-typescript", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /ExtensionDemo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /ExtensionDemo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "main": "dist/index.js", 4 | "scripts": { 5 | "test": "echo \"Error: no test specified\" && exit 1", 6 | "build": "webpack", 7 | "demo": "live-server ." 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "agora-rte-extension": "^1.0.23" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.16.7", 16 | "@babel/preset-env": "^7.16.11", 17 | "@babel/preset-typescript": "^7.16.7", 18 | "babel-loader": "^8.2.3", 19 | "typescript": "^4.6.3", 20 | "webpack": "^5.71.0", 21 | "webpack-cli": "^4.9.2", 22 | "live-server": "^1.2.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ExtensionDemo/src/audio-extension.ts: -------------------------------------------------------------------------------- 1 | import { AudioExtension, AudioProcessor, IAudioProcessorContext } from "agora-rte-extension"; 2 | 3 | class SimpleAudioExtension extends AudioExtension { 4 | protected _createProcessor(): SimpleAudioProcessor { 5 | return new SimpleAudioProcessor(); 6 | } 7 | } 8 | 9 | class SimpleAudioProcessor extends AudioProcessor { 10 | public name: string = "SimpleAudioProcessor"; 11 | private gainNode?: GainNode; 12 | private oscillatorNode?: OscillatorNode; 13 | 14 | protected onPiped(context: IAudioProcessorContext): void { 15 | const audioContext = context.getAudioContext(); 16 | const gainNode = audioContext.createGain(); 17 | const oscillatorNode = audioContext.createOscillator(); 18 | oscillatorNode.frequency.setValueAtTime(880, audioContext.currentTime); 19 | oscillatorNode.start(); 20 | oscillatorNode.connect(gainNode); 21 | 22 | this.gainNode = gainNode; 23 | this.oscillatorNode = oscillatorNode; 24 | } 25 | 26 | protected onUnpiped() { 27 | this.gainNode = undefined; 28 | this.oscillatorNode?.stop(); 29 | this.oscillatorNode = undefined; 30 | } 31 | 32 | protected onEnableChange(enabled: boolean): void | Promise { 33 | if (this.context && this.oscillatorNode && this.gainNode) { 34 | if (enabled) { 35 | this.oscillatorNode.connect(this.gainNode); 36 | } else { 37 | this.oscillatorNode.disconnect(); 38 | } 39 | } 40 | } 41 | 42 | protected onNode(audioNode: AudioNode, context: IAudioProcessorContext): void | Promise { 43 | if (this.gainNode && this.oscillatorNode) { 44 | audioNode.connect(this.gainNode); 45 | 46 | this.output(this.gainNode, context); 47 | } 48 | } 49 | } 50 | 51 | export { SimpleAudioExtension }; 52 | -------------------------------------------------------------------------------- /ExtensionDemo/src/index.ts: -------------------------------------------------------------------------------- 1 | import { SimpleAudioExtension } from "./audio-extension"; 2 | import { SimpleVideoExtension } from "./video-extension"; 3 | 4 | export { SimpleAudioExtension, SimpleVideoExtension }; 5 | -------------------------------------------------------------------------------- /ExtensionDemo/src/video-extension.ts: -------------------------------------------------------------------------------- 1 | import { Extension, VideoProcessor, Ticker, IProcessorContext } from "agora-rte-extension"; 2 | 3 | class SimpleVideoExtension extends Extension { 4 | protected _createProcessor(): SimpleVideoProcessor { 5 | return new SimpleVideoProcessor(); 6 | } 7 | } 8 | 9 | class SimpleVideoProcessor extends VideoProcessor { 10 | public name: string = "SimpleVideoProcessor"; 11 | private canvas: HTMLCanvasElement; 12 | private ctx: CanvasRenderingContext2D; 13 | private ticker: Ticker; 14 | private videoElement: HTMLVideoElement; 15 | private canvasTrack: MediaStreamTrack; 16 | 17 | public constructor() { 18 | super(); 19 | 20 | this.canvas = document.createElement("canvas"); 21 | this.canvas.width = 640; 22 | this.canvas.height = 480; 23 | this.ctx = this.canvas.getContext("2d")!; 24 | this.videoElement = document.createElement("video"); 25 | this.videoElement.muted = true; 26 | const outputStream = this.canvas.captureStream(30); 27 | this.canvasTrack = outputStream.getVideoTracks()[0]; 28 | 29 | this.ticker = new Ticker("RAF", 1000 / 30); 30 | this.ticker.add(this.process); 31 | } 32 | 33 | private process = () => { 34 | if (this.inputTrack) { 35 | this.ctx.drawImage(this.videoElement, 0, 0); 36 | this.ctx.fillStyle = "red"; 37 | this.ctx.fillRect(0, 0, 100, 100); 38 | } 39 | }; 40 | 41 | protected onEnableChange(enabled: boolean): void | Promise { 42 | if (!this.context) { 43 | return; 44 | } 45 | 46 | if (enabled) { 47 | this.ticker.start(); 48 | this.output(this.canvasTrack, this.context); 49 | } else { 50 | this.ticker.stop(); 51 | if (this.inputTrack) { 52 | this.output(this.inputTrack, this.context); 53 | } 54 | } 55 | } 56 | 57 | protected onTrack(track: MediaStreamTrack, ctx: IProcessorContext): void | Promise { 58 | this.videoElement.srcObject = new MediaStream([track]); 59 | this.videoElement.play(); 60 | this.videoElement.onplaying = () => { 61 | this.canvas.width = this.videoElement.videoWidth; 62 | this.canvas.height = this.videoElement.videoHeight; 63 | }; 64 | 65 | if (this.enabled) { 66 | this.ticker.start(); 67 | this.output(this.canvasTrack, ctx); 68 | } else { 69 | this.ticker.stop(); 70 | this.output(track, ctx); 71 | } 72 | } 73 | 74 | protected onUnpiped(): void { 75 | this.ticker.stop(); 76 | } 77 | } 78 | 79 | export { SimpleVideoExtension }; 80 | -------------------------------------------------------------------------------- /ExtensionDemo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "module": "umd", 7 | "lib": ["es2015", "es2016", "es2017", "dom"], 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "strictPropertyInitialization": true, 11 | "sourceMap": false, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "allowJs": true, 15 | "emitDeclarationOnly": true, 16 | "allowSyntheticDefaultImports": true, 17 | "experimentalDecorators": true, 18 | "emitDecoratorMetadata": true, 19 | "esModuleInterop": true, 20 | "stripInternal": true, 21 | "isolatedModules": true, 22 | "outDir": "./dist", 23 | "typeRoots": ["node_modules/@types"] 24 | }, 25 | "include": ["src/**/*"] 26 | } 27 | -------------------------------------------------------------------------------- /ExtensionDemo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const ROOT = __dirname; 3 | 4 | module.exports = { 5 | mode: "production", 6 | entry: path.resolve(ROOT, "src", "index.ts"), 7 | output: { 8 | path: path.resolve(ROOT, "dist"), 9 | filename: "index.js", 10 | library: { 11 | type: "umd", 12 | name: "SimpleExtension", 13 | }, 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /.ts$/, 19 | include: path.resolve(ROOT, "src"), 20 | exclude: /node_modules/, 21 | loader: "babel-loader", 22 | }, 23 | ], 24 | }, 25 | resolve: { 26 | extensions: [".ts", ".js"], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /README.cn.md: -------------------------------------------------------------------------------- 1 | # Agora RTC Web SDK 4.x 示例项目 2 | 3 | _[English](README.md) | 简体中文_ 4 | 5 | ## 简介 6 | 7 | 此仓库包含基于 Agora RTC Web SDK 4.x 的示例项目。 8 | 9 |
10 | 11 | Web SDK 4.x 是基于 Web SDK 3.x 开发的全量重构版本,在继承了 Web SDK 3.x 功能的基础上,优化了 SDK 的内部架构,提高了 API 的易用性。 12 | 13 | Web SDK 4.x 具有以下优势: 14 | 15 | - 面向开发者提供更先进的 API 架构和范式。 16 | - 所有异步场景的 API 使用 Promise 替代 Callback,提升集成代码的质量和健壮性。 17 | - 优化频道事件通知机制,统一频道内事件的命名和回调参数的格式,降低断线重连的处理难度。 18 | - 提供清晰和完善的错误码,方便错误排查。 19 | - 支持 TypeScript。 20 | 21 | ## 示例项目(使用 jQuery 和 Bootstrap) 22 | 23 | | 功能 | 示例项目位置 | 24 | | -------------- | ------------------------------------------------------------ | 25 | | 基础示例 | [/src/example/basic](/src/example/basic) | 26 | | 进阶示例 | [/src/example/advanced](/src/example/advanced) | 27 | | 插件示例 | [/src/example/plugin](/src/example/plugin) | 28 | | 其他示例 | [/src/example/others](/src/example/others) | 29 | | vue 框架示例 | [/src/example/framework/vue](/src/example/framework/vue) | 30 | | react 框架示例 | [/src/example/framework/react](/src/example/framework/react) | 31 | 32 | ### 如何运行示例项目 33 | 34 | #### 前提条件 35 | 36 | - 你必须使用 SDK 支持的浏览器运行示例项目。 关于支持的浏览器列表参考 [浏览器兼容性和已知问题](https://doc.shengwang.cn/doc/rtc/javascript/overview/browser-compatibility)。 37 | 38 | #### 运行步骤 39 | 40 | 1. 在项目根路径运行下面的命令安装依赖项。 41 | 42 | ```shell 43 | npm install 44 | ``` 45 | 46 | 2. 运行下面的命令启动示例项目。 47 | 48 | ```shell 49 | npm run dev 50 | ``` 51 | 52 | 3. 在浏览器打开 [http://localhost:3001/index.html](http://localhost:3001/index.html) 53 | 54 | 4. 在示例项目设置页面上,输入 App ID 和 App Certificate,然后点击设置按钮。 55 | - 关于 App ID 和 App Certificate 的获取方法参考 [开始使用 Agora 平台](https://docs.agora.io/cn/Agora%20Platform/get_appid_token)。 56 | 57 | ## 参考 58 | 59 | - [Web SDK 4.x 产品概述](https://doc.shengwang.cn/doc/rtc/javascript/overview/product-overview) 60 | - [Web SDK 4.x API 参考](https://doc.shengwang.cn/api-ref/rtc/javascript/overview) 61 | - [基于本仓库部署的在线 demo](https://webdemo.agora.io/) 62 | 63 | ## 反馈 64 | 65 | 如果你有任何问题或建议,可以通过 issue 的形式反馈。 66 | 67 | ## 相关资源 68 | 69 | - 你可以先参阅 [常见问题](https://doc.shengwang.cn/faq/list?category=integration-issues&platform=javascript&product=rtc) 70 | - 如果你想了解更多官方示例,可以参考 [官方 SDK 示例](https://github.com/AgoraIO) 71 | - 如果你想了解声网 SDK 在复杂场景下的应用,可以参考 [官方场景案例](https://github.com/AgoraIO-usecase) 72 | - 如果你想了解声网的一些社区开发者维护的项目,可以查看 [社区](https://github.com/AgoraIO-Community) 73 | - 若遇到问题需要开发者帮助,你可以到 [开发者社区](https://rtcdeveloper.com/) 提问 74 | - 如果需要售后技术支持, 你可以在 [Agora Dashboard](https://console.shengwang.cn/overview) 提交工单 75 | 76 | ## 许可证 77 | 78 | 示例项目遵守 MIT 许可证。 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample projects for Agora RTC Web SDK 4.x 2 | 3 | _English | [简体中文](README.cn.md)_ 4 | 5 | ## Overview 6 | 7 | This repository contains sample projects using the Agora RTC Web SDK 4.x. 8 | 9 |
10 | 11 | The Web SDK 4.x refactors the Web SDK 3.x. Based on the features of 3.x, 4.x fully optimizes the internal architecture of the SDK and provides more flexible and easy-to-use APIs. 12 | 13 | Compared to the Web SDK 3.x, the Web SDK 4.x has the following advantages: 14 | 15 | - Uses promises for asynchronous operations, which improves the robustness and readability of your code. 16 | - Supports TypeScript. 17 | - Replaces the Stream object with Track objects for separate and flexible control over audio and video. 18 | - Improves the channel event notification mechanism, making it easier for you to deal with reconnection. 19 | - Provides more accurate and comprehensive error codes for troubleshooting. 20 | 21 | ## Projects using jQuery and Bootstrap 22 | 23 | | Feature | Sample project location | 24 | | ----------------------- | ------------------------------------------------------------ | 25 | | Basic Examples | [/src/example/basic](/src/example/basic) | 26 | | Advanced Examples | [/src/example/advanced](/src/example/advanced) | 27 | | Plugin Examples | [/src/example/plugin](/src/example/plugin) | 28 | | Other Examples | [/src/example/others](/src/example/others) | 29 | | Vue Framework Example | [/src/example/framework/vue](/src/example/framework/vue) | 30 | | React Framework Example | [/src/example/framework/react](/src/example/framework/react) | 31 | 32 | ### How to run the sample projects 33 | 34 | #### Prerequisites 35 | 36 | You need a supported browser to run the sample projects. See [Product Overview](https://docs.agora.io/en/Interactive%20Broadcast/product_live?platform=Web#compatibility) for a list of supported browsers. 37 | 38 | #### Steps to run 39 | 40 | 1. In the project root path run the following command to install dependencies. 41 | 42 | ```shell 43 | npm install 44 | ``` 45 | 46 | 2. Use the following command to run the sample project. 47 | 48 | ```shell 49 | npm run dev 50 | ``` 51 | 52 | 3. Open link [http://localhost:3001/index.html](http://localhost:3001/index.html) in browser. 53 | 54 | 4. In the demo setting page, enter your App ID and App Certificate, then click SetUp button. 55 | - See [Get Started with Agora](https://docs.agora.io/en/Agora%20Platform/get_appid_token) to learn how to get an App ID and App Certificate. 56 | 57 | ## Reference 58 | 59 | - [Web SDK 4.x Product Overview](https://docs.agora.io/en/Interactive%20Broadcast/product_live?platform=Web) 60 | - [Web SDK 4.x API Reference](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/web_ng/index.html) 61 | - [Online demo deployed from this repo](https://webdemo.agora.io/) 62 | 63 | ## Feedback 64 | 65 | If you have any problems or suggestions regarding the sample projects, feel free to file an issue. 66 | 67 | ## Related resources 68 | 69 | - Check our [FAQ](https://docs.agora.io/en/faq) to see if your issue has been recorded. 70 | - Dive into [Agora SDK Samples](https://github.com/AgoraIO) to see more tutorials 71 | - Take a look at [Agora Use Case](https://github.com/AgoraIO-usecase) for more complicated real use case 72 | - Repositories managed by developer communities can be found at [Agora Community](https://github.com/AgoraIO-Community) 73 | - If you encounter problems during integration, feel free to ask questions in [Stack Overflow](https://stackoverflow.com/questions/tagged/agora.io) 74 | 75 | ## License 76 | 77 | The sample projects are under the MIT license. 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-examples-web", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node ./scripts/server.js", 8 | "pure": "node ./scripts/pure.js", 9 | "lint": "prettier . --write --ignore-unknown" 10 | }, 11 | "devDependencies": { 12 | "express": "^4.18.2", 13 | "prettier": "^3.3.3" 14 | }, 15 | "author": "qz", 16 | "license": "ISC", 17 | "packageManager": "yarn@1.22.22" 18 | } 19 | -------------------------------------------------------------------------------- /scripts/pure.js: -------------------------------------------------------------------------------- 1 | // TIP: This script is used to purge all SSO-related code from the project. 2 | // This file does not require attention. 3 | 4 | const { readdirSync, existsSync, readFileSync, writeFileSync, statSync } = require("fs"); 5 | const path = require("path"); 6 | const { exec } = require("child_process"); 7 | 8 | const TARGET_DIR = path.resolve(__dirname, "../src"); 9 | const REG_SSO_SCRIPT = /^\s+\ from html file 81 | const removeSSOScriptTag = (htmlPath) => { 82 | console.log(htmlPath); 83 | let htmlContent = readFileSync(htmlPath, "utf-8"); 84 | htmlContent = htmlContent.replace(REG_SSO_SCRIPT, ""); 85 | writeFileSync(htmlPath, htmlContent); 86 | }; 87 | 88 | const removeSSOCode = (jsPath) => { 89 | console.log(jsPath); 90 | let jsContent = readFileSync(jsPath, "utf-8"); 91 | jsContent = jsContent.replace(REG_SSO_CODE, ""); 92 | writeFileSync(jsPath, jsContent); 93 | }; 94 | 95 | function run() { 96 | console.log("----- pure run start -----"); 97 | delSSORelatedFile(); 98 | delSSORelatedHTMLCode(); 99 | delSSORelatedJSCode(); 100 | console.log("----- pure run end -----"); 101 | } 102 | 103 | run(); 104 | -------------------------------------------------------------------------------- /scripts/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const path = require("path"); 3 | 4 | // change the port if necessary 5 | const PORT = 3001; 6 | const URL = `http://localhost:${PORT}/index.html`; 7 | 8 | const dir = path.join(__dirname, "../src"); 9 | const app = express(); 10 | app.use(express.static(dir)); 11 | 12 | app.listen(PORT, () => { 13 | console.info(`\n---------------------------------------\n`); 14 | console.info(`please visit: ${URL}`); 15 | console.info(`\n---------------------------------------\n`); 16 | }); 17 | -------------------------------------------------------------------------------- /src/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0] - 2024-11-28 4 | 5 | 1. Address the issue of screen sharing being unavailable in Safari. 6 | 2. Disable the autoplay of custom videos in the mobile version of Safari. 7 | 3. Enable the selection of MP3 files on iOS devices for audio effect testing. 8 | 4. Rectify the problem of the device still capturing from the camera and microphone when not in use. 9 | 5. Resolve the inability to adjust audio sound effects on mobile devices. 10 | 6. Replace VAD with Voice Activity Detection. 11 | 7. Introduce a dropdown menu for selecting to subscribe to remote users. 12 | 8. Add a direct link to GitHub for convenient access. 13 | 9. Support the addition of dynamic tokens for channel entry. 14 | 15 | ## [2.1.0] - 2024-12-30 16 | 17 | 1. Add translate 18 | 2. Add rule verification for appid 19 | 3. Enter appid to save automatically 20 | 4. Corrected some boot links 21 | -------------------------------------------------------------------------------- /src/assets/agora-logo-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO/API-Examples-Web/92692ec78a3b53c2398b6e6508e4bf86b9d4ca54/src/assets/agora-logo-en.png -------------------------------------------------------------------------------- /src/assets/agora-logo-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO/API-Examples-Web/92692ec78a3b53c2398b6e6508e4bf86b9d4ca54/src/assets/agora-logo-zh.png -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO/API-Examples-Web/92692ec78a3b53c2398b6e6508e4bf86b9d4ca54/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO/API-Examples-Web/92692ec78a3b53c2398b6e6508e4bf86b9d4ca54/src/assets/github.png -------------------------------------------------------------------------------- /src/common/left-menu.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_ZH_DOC_URL = "https://doc.shengwang.cn/doc/rtc/javascript/get-started/enable-service"; 2 | const DEFAULT_EN_DOC_URL = 3 | "https://docs.agora.io/en/video-calling/get-started/authentication-workflow?platform=web"; 4 | 5 | function __isSmallScreen() { 6 | const SM_WIDTH = 576; // small screen width (phone) 7 | const windowWidth = $(window).width(); 8 | return windowWidth <= SM_WIDTH; 9 | } 10 | 11 | function __transListToMenuDom(list) { 12 | return list 13 | .map((item) => { 14 | return ``; 31 | }) 32 | .join(""); 33 | } 34 | 35 | function __initMenu() { 36 | let language = getLanguage(); 37 | let href = ""; 38 | let logoImgSrc = ""; 39 | if (language == "en") { 40 | href = DEFAULT_EN_DOC_URL; 41 | logoImgSrc = `${ORIGIN_URL}/assets/agora-logo-en.png`; 42 | } else { 43 | href = DEFAULT_ZH_DOC_URL; 44 | logoImgSrc = `${ORIGIN_URL}/assets/agora-logo-zh.png`; 45 | } 46 | let logoHtml = `logo`; 47 | let menuHtml = ` 48 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/example/framework/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Use React -- Agora 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/example/others/dualStream/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Dual Stream -- Agora 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
local stream
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
remote stream
33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 | 51 |
52 | If you don`t know what is your channel, checkout 53 | this 54 |
55 |
56 |
57 | 58 | 66 |
67 |
68 |
69 | 72 | 75 |
76 |
Click Remote User Video Player Change Stream Type
77 |
78 |
79 | 80 |
81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/example/others/dualStream/index.js: -------------------------------------------------------------------------------- 1 | // create Agora client 2 | var client = AgoraRTC.createClient({ 3 | mode: "rtc", 4 | codec: "vp8", 5 | }); 6 | AgoraRTC.enableLogUpload(); 7 | var localTracks = { 8 | videoTrack: null, 9 | audioTrack: null, 10 | }; 11 | var remoteUsers = {}; 12 | // Agora client options 13 | var options = getOptionsFromLocal(); 14 | 15 | $("body").on("click", ".player", async function (e) { 16 | e.preventDefault(); 17 | const player = e.target.parentElement.parentElement; 18 | const uid = Number(player.dataset.uid); 19 | 20 | try { 21 | // Set user clicked to HQ/LQ Stream 22 | await changeSomeUserStream(uid); 23 | } catch (error) { 24 | console.error(error); 25 | message.error(error.message); 26 | } 27 | }); 28 | 29 | $("#join-form").submit(async function (e) { 30 | e.preventDefault(); 31 | $("#join").attr("disabled", true); 32 | try { 33 | options.channel = $("#channel").val(); 34 | options.uid = Number($("#uid").val()); 35 | options.token = await agoraGetAppData(options); 36 | await join(); 37 | setOptionsToLocal(options); 38 | message.success("join channel success!"); 39 | } catch (error) { 40 | console.error(error); 41 | message.error(error.message); 42 | } finally { 43 | $("#leave").attr("disabled", false); 44 | } 45 | }); 46 | 47 | $("#leave").click(function (e) { 48 | leave(); 49 | }); 50 | 51 | async function join() { 52 | // add event listener to play remote tracks when remote user publishs. 53 | client.on("user-published", handleUserPublished); 54 | client.on("user-unpublished", handleUserUnpublished); 55 | 56 | // start Proxy if needed 57 | const mode = Number(options.proxyMode); 58 | if (mode != 0 && !isNaN(mode)) { 59 | client.startProxyServer(mode); 60 | } 61 | 62 | // Customize the video profile of the low-quality stream: 160 × 120, 15 fps, 120 Kbps. 63 | client.setLowStreamParameter({ 64 | width: 160, 65 | height: 120, 66 | framerate: 15, 67 | bitrate: 120, 68 | }); 69 | 70 | // Enable dual-stream mode. 71 | await client.enableDualStream(); 72 | 73 | // join a channel and create local tracks, we can use Promise.all to run them concurrently 74 | [options.uid, localTracks.audioTrack, localTracks.videoTrack] = await Promise.all([ 75 | // join the channel 76 | client.join(options.appid, options.channel, options.token || null, options.uid || null), 77 | // create local tracks, using microphone and camera 78 | AgoraRTC.createMicrophoneAudioTrack({ 79 | encoderConfig: "music_standard", 80 | }), 81 | AgoraRTC.createCameraVideoTrack(), 82 | ]); 83 | 84 | // play local video track 85 | localTracks.videoTrack.play("local-player"); 86 | $("#local-player-name").text(`uid: ${options.uid}`); 87 | 88 | // publish local tracks to channel 89 | await client.publish(Object.values(localTracks)); 90 | console.log("publish success"); 91 | } 92 | 93 | async function changeSomeUserStream(uid) { 94 | const streamType = $(`#stream-type-${uid}`).text(); 95 | if (streamType == "HQ Stream") { 96 | // Set the remote user to the low-quality video stream. 97 | await client.setRemoteVideoStreamType(uid, 1); 98 | $(`#stream-type-${uid}`).text("LQ Stream"); 99 | } else if (streamType == "LQ Stream") { 100 | // Set the remote user to the high-quality video stream. 101 | await client.setRemoteVideoStreamType(uid, 0); 102 | $(`#stream-type-${uid}`).text("HQ Stream"); 103 | } 104 | } 105 | 106 | async function leave() { 107 | for (trackName in localTracks) { 108 | var track = localTracks[trackName]; 109 | if (track) { 110 | track.stop(); 111 | track.close(); 112 | localTracks[trackName] = undefined; 113 | } 114 | } 115 | 116 | // remove remote users and player views 117 | remoteUsers = {}; 118 | $("#remote-playerlist").html(""); 119 | 120 | // disable dual-stream mode 121 | await client.disableDualStream(); 122 | 123 | // leave the channel 124 | await client.leave(); 125 | $("#local-player-name").text(""); 126 | $("#join").attr("disabled", false); 127 | $("#leave").attr("disabled", true); 128 | console.log("client leaves channel success"); 129 | } 130 | 131 | async function subscribe(user, mediaType) { 132 | const uid = user.uid; 133 | // subscribe to a remote user 134 | await client.subscribe(user, mediaType); 135 | console.log("subscribe success"); 136 | if (mediaType === "video") { 137 | const player = $(` 138 |
139 |
140 |
uid: ${uid} 141 | HQ Stream 142 |
143 |
144 |
145 | `); 146 | $("#remote-playerlist").append(player); 147 | user.videoTrack.play(`player-${uid}`); 148 | } 149 | if (mediaType === "audio") { 150 | user.audioTrack.play(); 151 | } 152 | } 153 | 154 | function handleUserPublished(user, mediaType) { 155 | const id = user.uid; 156 | remoteUsers[id] = user; 157 | subscribe(user, mediaType); 158 | } 159 | 160 | function handleUserUnpublished(user, mediaType) { 161 | if (mediaType === "video") { 162 | const id = user.uid; 163 | delete remoteUsers[id]; 164 | $(`#player-wrapper-${id}`).remove(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/example/others/pushStreamToCDN/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Push Stream To CDN -- Agora 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
local stream
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
remote stream
33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 | 51 |
52 | If you don`t know what is your channel, checkout 53 | this 54 |
55 |
56 |
57 | 58 | 66 |
67 |
68 | 69 | 76 |
77 |
78 |
79 | 80 | 83 |
84 |
85 | 93 | 101 |
102 |
103 |
104 | 105 |
106 |
107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/example/quickStart/videoAndVoiceCalling/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Basic Live -- Agora 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
local stream
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
remote stream
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 | 44 | 51 |
52 | If you don`t know what is your channel, checkout 53 | this 54 |
55 |
56 |
57 |
User ID(optional)
58 | 66 |
67 |
68 | 69 | 76 |
77 |
78 | 81 | 84 |
85 | 86 |
87 |
Video
88 |
89 | 92 | 95 |
96 |
97 | 98 |
99 |
Audio
100 |
101 | 104 | 107 |
108 |
109 |
110 |
111 | 112 |
113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/i18n/en/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "settingMenu": "Setting", 3 | "quickMenu": "Quick Start", 4 | "basicMenu": "Basic Examples", 5 | "videoAndVoiceCalling": "Video and Voice Calling", 6 | "interactiveLiveStreamingStandard":"Interactive Live Streaming Standard", 7 | "interactiveLiveStreamingPremium":"Interactive Live Streaming Premium", 8 | "advancedMenu": "Advanced Examples", 9 | "othersMenu": "Other Examples", 10 | "extensionMenu": "Plugin Examples", 11 | "frameworkMenu": "Framework Examples", 12 | "initializeSettings": "Initialize Settings", 13 | "basicVoiceCall": "Basic Voice Calling", 14 | "basicVideoCall": "Basic Video Calling", 15 | "basicLive": "Basic Live Streaming", 16 | "shareTheScreen": "Screen Share", 17 | "testMediaDevices": "Test/Switch Media Devices", 18 | "adjustVideoProfile": "Adjust Video Profile", 19 | "displayCallStats": "Display In-call Statistics", 20 | "audioEffects": "Audio Effects", 21 | "joinMultipleChannel": "Join Multiple Channels", 22 | "customVideoSource": "Custom Video Source", 23 | "selfRendering": "Video Self-Rendering", 24 | "selfCapturing": "Video Self-Capturing", 25 | "screenshot": "Video Screenshot", 26 | "geoFencing": "Network Geofencing", 27 | "pushStreamToCDN": "Push Streams to CDN", 28 | "dualStream": "Enable Dual-stream Mode", 29 | "virtualBackground": "Virtual Background", 30 | "beauty": "Beauty", 31 | "aiDenoiser": "AI Denoiser", 32 | "superClarity": "Super Clarity", 33 | "spatialAudio": "Spatial Audio", 34 | "vad": "Voice Activity Detection", 35 | "videoCompositor": "Local Video Compositing", 36 | "vue": "Vue", 37 | "react": "React", 38 | "setting": "SetUp", 39 | "cloudProxy": "Cloud Proxy", 40 | "certificate": "APP Certificate(optional)", 41 | "language": "Language", 42 | "agora": "Agora", 43 | "clickChange": "Click to change", 44 | "needHelp": "If you need help, please visit the official website", 45 | "agoraDocs": "Agora Docs", 46 | "sampleCode": "Sample Code", 47 | "stt": "Speech To Text", 48 | "autoPlayFailed": " The autoplay has failed; please click to initiate playback.", 49 | "appIdTips": "If you don`t know what is your appid, checkout this", 50 | "appCertificateTips": " If you don`t know what is your app certificate, checkout this", 51 | "cloudProxyTips": "If you don`t know what is Cloud Proxy, checkout this", 52 | "chooseCustom": "Choose Custom Option to manually enter the project's App ID and App Certificate (optional).", 53 | "and": "and" 54 | } -------------------------------------------------------------------------------- /src/i18n/language.js: -------------------------------------------------------------------------------- 1 | let LANGUAGE_CACHE_KEY = "__language_data__"; 2 | let LANGUAGE_CACHE_DATA = JSON.parse(sessionStorage.getItem(LANGUAGE_CACHE_KEY)) || {}; 3 | 4 | function __insertIe8nText(data) { 5 | var insertEle = $(".i18n"); 6 | insertEle.each(function () { 7 | let dataName = $(this).attr("name"); 8 | let i18nTxt = data[dataName]; 9 | $(this).html(i18nTxt); 10 | }); 11 | } 12 | 13 | function __getLanguageData(language, success, fail) { 14 | let url = ""; 15 | if (LANGUAGE_CACHE_DATA[language] && LANGUAGE_CACHE_DATA[language].sampleCode) { 16 | success(LANGUAGE_CACHE_DATA[language]); 17 | return; 18 | } 19 | 20 | switch (language) { 21 | case "zh": 22 | url = `${ORIGIN_URL}/i18n/zh-CN/index.json`; 23 | break; 24 | case "zh-CN": 25 | url = `${ORIGIN_URL}/i18n/zh-CN/index.json`; 26 | break; 27 | case "en": 28 | url = `${ORIGIN_URL}/i18n/en/index.json`; 29 | break; 30 | default: 31 | url = `${ORIGIN_URL}/i18n/en/index.json`; 32 | } 33 | 34 | $.ajax({ 35 | url: url, 36 | async: true, 37 | cache: false, 38 | dataType: "json", 39 | success: success, 40 | error: fail, 41 | }); 42 | } 43 | 44 | function __execI18n() { 45 | const language = getLanguage(); 46 | 47 | const success = function (data, status) { 48 | LANGUAGE_CACHE_DATA[language] = data; 49 | sessionStorage.setItem(LANGUAGE_CACHE_KEY, JSON.stringify(LANGUAGE_CACHE_DATA)); 50 | __insertIe8nText(data); 51 | }; 52 | 53 | const fail = function (jqXHR, textStatus, errorThrown) { 54 | let msg = "get language data failed!"; 55 | let href = window.location.href; 56 | if (/^file/.test(href)) { 57 | msg += "can not get language data from local file, please use http server!"; 58 | } 59 | console.error(msg, jqXHR, textStatus); 60 | }; 61 | 62 | __getLanguageData(language, success, fail); 63 | } 64 | 65 | function __insertLanguageSwitch() { 66 | const languageSwitchHtml = `
67 | 70 | 74 |
`; 75 | 76 | $("#language-switch-wrapper").html(languageSwitchHtml); 77 | 78 | const language = getLanguage(); 79 | 80 | if (language == "en") { 81 | $("#language-english").addClass("active"); 82 | $("#language-text").text("English"); 83 | } else { 84 | $("#language-zh").addClass("active"); 85 | $("#language-text").text("简体中文"); 86 | } 87 | 88 | function switchLanguage(v) { 89 | if (language == v) { 90 | return; 91 | } 92 | let options = getOptionsFromLocal(); 93 | if (v == "zh-CN" || v == "zh") { 94 | $("#language-zh").addClass("active"); 95 | $("#language-english").removeClass("active"); 96 | $("#language-text").text("简体中文"); 97 | options.language = "zh-CN"; 98 | setOptionsToLocal(options); 99 | } else { 100 | $("#language-english").addClass("active"); 101 | $("#language-zh").removeClass("active"); 102 | $("#language-text").text("English"); 103 | options.language = "en"; 104 | setOptionsToLocal(options); 105 | } 106 | window.location.reload(); 107 | } 108 | 109 | $("#language-english").click(function (e) { 110 | switchLanguage("en"); 111 | }); 112 | 113 | $("#language-zh").click(function (e) { 114 | switchLanguage("zh-CN"); 115 | }); 116 | } 117 | 118 | window.addEventListener("pageshow", function (e) { 119 | __insertLanguageSwitch(); 120 | __execI18n(); 121 | }); 122 | -------------------------------------------------------------------------------- /src/i18n/zh-CN/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "settingMenu": "设置", 3 | "quickMenu": "快速开始", 4 | "basicMenu": "基础示例", 5 | "videoAndVoiceCalling": "音视频通话", 6 | "interactiveLiveStreamingStandard":"极速直播", 7 | "interactiveLiveStreamingPremium":"互动直播", 8 | "advancedMenu": "进阶示例", 9 | "othersMenu": "其他示例", 10 | "extensionMenu": "插件示例", 11 | "frameworkMenu": "框架示例", 12 | "initializeSettings": "初始化设置", 13 | "basicVoiceCall": "基础语音通话", 14 | "basicVideoCall": "基础视频通话", 15 | "basicLive": "基础视频直播", 16 | "shareTheScreen": "屏幕共享", 17 | "testMediaDevices": "音视频设备检测", 18 | "adjustVideoProfile": "视频编码属性", 19 | "displayCallStats": "通话中质量检测", 20 | "audioEffects": "音频音效", 21 | "joinMultipleChannel": "加入多频道", 22 | "customVideoSource": "自定义视频源", 23 | "selfRendering": "视频自渲染", 24 | "selfCapturing": "视频自采集", 25 | "screenshot": "视频截图", 26 | "geoFencing": "区域访问限制", 27 | "pushStreamToCDN": "推流到CDN", 28 | "dualStream": "大小流", 29 | "virtualBackground": "虚拟背景", 30 | "beauty": "美颜", 31 | "aiDenoiser": "AI降噪", 32 | "superClarity": "AI画质", 33 | "spatialAudio": "空间音频", 34 | "vad": "语音活动检测", 35 | "videoCompositor": "本地合图", 36 | "vue": "使用Vue", 37 | "react": "使用React", 38 | "setting": "设置", 39 | "cloudProxy": "云代理", 40 | "certificate": "App 证书 (可选)", 41 | "language": "语言", 42 | "agora": "声网", 43 | "clickChange": "点击修改", 44 | "needHelp": "需要帮助请跳转至", 45 | "agoraDocs": "声网文档", 46 | "stt": "语音转文字", 47 | "autoPlayFailed": "自动播放失败,点击即可播放。", 48 | "appIdTips": "点击这里了解App ID", 49 | "appCertificateTips": "点击这里了解App 证书", 50 | "cloudProxyTips": "点击这里了解云代理", 51 | "chooseCustom": "选择自定义,可手动填写项目的 App ID 和 App 证书(如有)。", 52 | "sampleCode": "示例代码", 53 | "and": "和" 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Agora WebSDK Demos 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 |
35 | 36 | 37 | 39 |
40 |
41 |
42 | 43 |
44 | 45 | 46 |
47 |
48 |
49 |
50 | 53 |
54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | let options = getOptionsFromLocal(); 2 | let modeList = [ 3 | { 4 | label: "Off", 5 | detail: "Disable Cloud Proxy", 6 | value: "0", 7 | }, 8 | { 9 | label: "UDP Mode", 10 | detail: "Enable Cloud Proxy via UDP protocol", 11 | value: "3", 12 | }, 13 | { 14 | label: "TCP Mode", 15 | detail: "Enable Cloud Proxy via TCP/TLS port 443", 16 | value: "5", 17 | }, 18 | ]; 19 | let proxyModeItem; 20 | let projectList = []; 21 | 22 | $(() => { 23 | initVersion(); 24 | initModes(); 25 | initDocUrl(); 26 | }); 27 | 28 | 29 | 30 | const saveConfig = () => { 31 | options.appid = escapeHTML($("#appid").val().trim()); 32 | options.certificate = escapeHTML($("#certificate").val().trim()); 33 | options.proxyMode = proxyModeItem.value; 34 | setOptionsToLocal(options); 35 | } 36 | 37 | 38 | 39 | const checkAppId = () => { 40 | const projectAppIdType = $("#project-id-select").val(); 41 | if(projectAppIdType !== 'custom_settings'){ 42 | return true 43 | } 44 | // check appid 45 | const appId = $("#appid").val().trim(); 46 | if (!appId) { 47 | alert("Need to set up appID and appCertificate!"); 48 | return; 49 | } 50 | const isValidate = /^[A-Za-z0-9]{32}$/.test(appId); 51 | if (!isValidate) { 52 | alert("AppID verification failed, please enter correct information and try again!"); 53 | } 54 | return isValidate; 55 | 56 | } 57 | $(document).ready(function () { 58 | $(document).off('click', '.sidebar-bottom').on('click', '.sidebar-bottom', function (e) { 59 | if ($(e.target).closest('.sidebar-bottom').length) { 60 | saveConfig(); 61 | } 62 | 63 | const target = $(e.target).attr('data-href'); 64 | if (!target) { 65 | return; 66 | } 67 | const isValidate = checkAppId(); 68 | if (!isValidate) { 69 | return; 70 | } 71 | window.location.href = $(e.target).attr('data-href'); 72 | }); 73 | }); 74 | 75 | 76 | $(".proxy-list").change(function (e) { 77 | changeModes(this.value); 78 | saveConfig(); 79 | }); 80 | 81 | $("#setup-btn").click(function (e) { 82 | saveConfig(); 83 | const isValidate = checkAppId(); 84 | if (!isValidate) { 85 | return; 86 | } 87 | message.success("Set successfully! Link to function page!"); 88 | const href = getJumpBackUrl(); 89 | window.location.href = href; 90 | }); 91 | 92 | $("#appid").change(function (e) { 93 | saveConfig(); 94 | }) 95 | 96 | $("#certificate").change(function (e) { 97 | saveConfig(); 98 | }); 99 | 100 | $("#project-id-select").change(function (e) { 101 | const appId = this.value; 102 | if (appId === "custom_settings") { 103 | $("#appid").val(""); 104 | $("#certificate").val(""); 105 | $("#appid").prop("disabled", false); 106 | $("#certificate").prop("disabled", false); 107 | return; 108 | } else { 109 | $("#appid").prop("disabled", true); 110 | $("#certificate").prop("disabled", true); 111 | } 112 | const project = projectList.find((project) => project.appId === appId); 113 | $("#appid").val(project.appId); 114 | $("#certificate").val(project.appSecret); 115 | 116 | }); 117 | 118 | async function changeModes(label) { 119 | proxyModeItem = modeList.find((profile) => profile.label === label); 120 | } 121 | 122 | function initModes() { 123 | modeList.forEach((profile) => { 124 | $(".proxy-list").append( 125 | ``, 126 | ); 127 | }); 128 | proxyModeItem = modeList.find((profile) => profile.value === options.proxyMode) || modeList[0]; 129 | $(".proxy-list").val(proxyModeItem.label); 130 | } 131 | 132 | function initVersion() { 133 | const version = AgoraRTC.VERSION; 134 | $("#version-text").text(`v${version}`); 135 | } 136 | 137 | function appendCustomOption() { 138 | $("#project-id-select").append( 139 | ``, 140 | ) 141 | } 142 | function initDocUrl() { 143 | $("#certificate-link").attr("href", appCertificateLink); 144 | $("#appid-link").attr("href", appIdLink); 145 | $("#proxy-link").attr("href", proxyLink); 146 | 147 | $('.sidebar-bottom a').each(function () { 148 | const href = $(this).attr('href'); 149 | $(this).attr('data-href', href); 150 | $(this).removeAttr('href'); 151 | }); 152 | const observer = new MutationObserver((mutations) => { 153 | mutations.forEach((mutation) => { 154 | $('.sidebar-bottom a').each(function () { 155 | const href = $(this).attr('href'); 156 | $(this).attr('data-href', href); 157 | $(this).removeAttr('href'); 158 | }); 159 | if (mutation.type === 'childList') { 160 | const certificateLinkNode = document.getElementById('certificate-link'); 161 | const appidLinkNode = document.getElementById('appid-link'); 162 | const proxyLinkNode = document.getElementById('proxy-link'); 163 | if (certificateLinkNode) { 164 | certificateLinkNode.setAttribute('href', appCertificateLink); 165 | } 166 | if (appidLinkNode) { 167 | appidLinkNode.setAttribute('href', appIdLink); 168 | } 169 | if (proxyLinkNode) { 170 | proxyLinkNode.setAttribute('href', proxyLink); 171 | } 172 | if (certificateLinkNode && appidLinkNode && proxyLinkNode) { 173 | observer.disconnect(); 174 | } 175 | } 176 | }); 177 | }); 178 | observer.observe(document.body, { childList: true, subtree: true }); 179 | } 180 | 181 | let reGetProjects = false; 182 | --------------------------------------------------------------------------------