├── .gitignore ├── On-Premise-Recording-C++ ├── LICENSE.md ├── README.md └── README_zh.md └── On-Premise-Recording-Nodejs ├── .gitignore ├── README.md ├── README_zh.md ├── architecture.png ├── record ├── AgoraRecordSdk.js ├── build.sh ├── build_debug.sh ├── package-lock.json ├── sdkdemo.js └── src │ ├── README.md │ ├── agora_node_ext │ ├── AgoraSdk.cpp │ ├── AgoraSdk.h │ ├── agora_node_ext.cpp │ ├── agora_node_ext.h │ ├── agora_node_recording.cpp │ ├── agora_node_recording.h │ ├── node_async_queue.cpp │ ├── node_async_queue.h │ ├── node_log.cpp │ ├── node_log.h │ ├── node_napi_api.cpp │ ├── node_napi_api.h │ ├── node_uid.cpp │ ├── node_uid.h │ ├── opt_parser.cpp │ └── opt_parser.h │ └── binding.gyp └── server ├── app.js ├── package-lock.json ├── package.json └── recordManager.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /On-Premise-Recording-C++/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | Copyright (c) 2018 Agora Lab, Inc (http://www.agora.io/) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /On-Premise-Recording-C++/README.md: -------------------------------------------------------------------------------- 1 | # Agora Linux Server Recording 2 | 3 | *Read this in other languages: [中文](README_zh.md)* 4 | 5 | This sample application for the Agora Recording SDK enables recording on your Linux server. 6 | 7 | ## Prerequisites 8 | - Ubuntu 12.04+ x64 or CentOS 6.5+ x64 (CentOS 7+ recommended) 9 | - GCC 4.4+ 10 | - ARS IP (public IP) 11 | - 1MB+ bandwidth for each simultaneous recording channel 12 | - Server access for `qos.agoralab.co` 13 | 14 | **Note:** If server access is denied, the Agora SDK may fail to transfer the required data. 15 | 16 | ## Quick Start 17 | 18 | This section shows you how to prepare and build the sample application. 19 | 20 | - [Create an Account and Obtain an App ID](#create-an-account-and-obtain-an-app-id) 21 | - [Integrate the Agora Video SDK](#integrate-the-agora-video-sdk) 22 | - [Build the Sample Application](#build-the-sample-application) 23 | 24 | 25 | ### Create an Account and Obtain an App ID 26 | To build and run the sample application, first obtain an app ID: 27 | 28 | 1. Create a developer account at [agora.io](https://dashboard.agora.io/signin/). Once you finish the sign-up process, you are redirected to the dashboard. 29 | 2. In the dashboard tree on the left, navigate to **Projects** > **Project List**. 30 | 3. Save the app ID from the Agora dashboard. The app ID is needed to run the sample application. 31 | 32 | ### Integrate the Agora Video SDK 33 | 34 | To open and build the sample application, first integrate the Agora SDK. 35 | 36 | 1. Download the [Agora Recording SDK for Linux](https://www.agora.io/en/download/). 37 | 2. Unzip the downloaded SDK package. 38 | 39 | ### Run the Sample Application 40 | 41 | 1. Ensure the following UDP ports are open. (Required for communication with the Agora servers.) 42 | - 4001 43 | - 4030 44 | - 1080 45 | - 8000 46 | - 25000 47 | 2. Ensure local ports are not limited by firewalls. If limitations cannot be avoided, apply the following port settings: 48 | 49 | Port type|Setting 50 | ----|---- 51 | Receiving port|4000 - 41000 52 | Low UDP Port|40000 53 | High UDP Port|41000 54 | 55 | 3. Navigate to the `samples/cpp/release/bin` directory. Replace `PATH-TO-PROJECT` with the path to the sample application on your Linux server. 56 | 57 | ``` 58 | cd PATH-TO-PROJECT/samples/cpp/release/bin 59 | ``` 60 | 61 | 62 | 5. Run `./recorder`, using the app ID and path to the sample app directory. 63 | - Replace `APPID` with the app ID from the [earlier step](#create-an-account-and-obtain-an-app-id). 64 | - Replace `PATH-TO-PROJECT` with the path to the sample app directory on the Linux server. 65 | 66 | ``` 67 | ./recorder --appId APPID --uid 0 --channel mychannel --appliteDir PATH-TO-PROJECT/bin/AgoraCoreService 68 | ``` 69 | 70 | **Note:** To view additional Agora SDK help commands, run `cmds`. 71 | 72 | ``` 73 | cmds ./recorder 74 | ``` 75 | 76 | ## Resources 77 | - A detailed code walkthrough for this sample is available in [Steps to Create this Sample](./guide.md). 78 | - See full API documentation in the [Document Center](https://docs.agora.io/en/) 79 | - [File bugs about this sample](https://github.com/AgoraIO/Basic-Recording/issues) 80 | - See [detailed Agora Linux Recording guides](https://docs.agora.io/en/Recording/recording_integrate_cpp?platform=CPP) 81 | 82 | ## License 83 | This software is licensed under the MIT License (MIT). [View the license](LICENSE.md). 84 | -------------------------------------------------------------------------------- /On-Premise-Recording-C++/README_zh.md: -------------------------------------------------------------------------------- 1 | 2 | # Agora-LinuxServer-Recording 3 | 4 | *Read this in other languages: [English](README.md)* 5 | 6 | ## 准备工作 7 | 8 | 请确保满足以下操作系统要求: 9 | - Ubuntu 12.04 x64 或更高版本 10 | - CentOS 6.5 x64 或更高版本 11 | - gcc 4.4 或更高版本。 12 | - 与公网互通,并能够访问`qos.agoralab.co` 13 | - 每一个录制频道至少1MB+的带宽 14 | 15 | ## 快速开始 16 | 17 | ### 创建Agora账号并获取App ID 18 | - 在[Agora开发者中心](https://dashboard.agora.io/cn/signup/) 注册账号 19 | - 在[我的主页]-->[项目管理]创建自己的项目,获取到 AppID 20 | - 该AppID用于运行Agora应用程序 21 | 22 | **Note:** 该AppID用于运行你的Agora项目,请妥善保存,勿外泄。 23 | 24 | ### 下载Agora Recording SDK 25 | - 下载[录制SDK](https://docs.agora.io/cn/Agora%20Platform/downloads) 26 | - 解压下载的SDK包 27 | 28 | ### 运行 29 | 30 | 1. 确保以下的UDP端口是打开的(这些端口用来与Agora服务器之间通信) 31 | - 4001 32 | - 4030 33 | - 1080 34 | - 8000 35 | - 25000 36 | 37 | 2. 确保本地端口没有被防火墙禁掉。如果启用了防火墙,则需要放开Agora录制SDK设定的端口范围。你可以为多个录制进程统一配置较大的端口范围(Agora 建议 40000 ~ 41000 或更大)。此时,录制 SDK 会在指定范围内分配录制端口。设置端口范围,你需要配置参数 lowUdpPort 和highUdpPort。 38 | 39 | 端口类型|范围 40 | ----|---- 41 | 接收码流端口|40000 - 41000 42 | Low UDP Port|40000 43 | High UDP Port|41000 44 | 45 | 3. 进入`samples/cpp/release/bin`目录下,你可以看到`recorder`可执行程序 46 | 47 | 4. 运行`./recorder`,即可看到相关用法,填上相应的参数即可进行录制 48 | ``` 49 | ./recorder --appId APPID --uid 0 --channel mychannel --appliteDir PATH-TO-PROJECT/bin/AgoraCoreService 50 | ``` 51 | 52 | ## 快速通道 53 | **当前sample** 54 | - 进入`samples`,执行`make`编译,然后运行编译生成的`./recorder`, 即可看到相关用法 55 | 56 | ## 联系我们 57 | 58 | - 详细信息请参考 [录制快速入门](https://docs.agora.io/cn/Recording/product_recording?platform=All%20Platforms) 59 | - 完整的 API 文档见 [文档中心](https://docs.agora.io/cn/) 60 | - 如果在集成中遇到问题, 你可以到 [开发者社区](https://dev.agora.io/cn/) 提问 61 | - 如果有售前咨询问题, 可以拨打 400 632 6626,或加入官方Q群 12742516 提问 62 | - 如果需要售后技术支持, 你可以在 [Agora Dashboard](https://dashboard.agora.io) 提交工单 63 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | sdk 4 | AgoraCoreService 5 | *.node 6 | output 7 | .vscode 8 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/README.md: -------------------------------------------------------------------------------- 1 | # Agora Restful Recording Nodejs 2 | 3 | *Read this in other languages: [中文](README_zh.md)* 4 | 5 | This sample application works as a simple restful server to manage recording in an easy way. 6 | 7 | ## Prerequisites 8 | - Ubuntu 12.04+ x64 or CentOS 6.5+ x64 (CentOS 7+ recommended) 9 | - GCC 4.4+ 10 | - ARS IP (public IP) 11 | - 1MB+ bandwidth for each simultaneous recording channel 12 | - Server access for `qos.agoralab.co` 13 | - NodeJS 8.9+ 14 | 15 | **Note:** If server access is denied, the Agora SDK may fail to transfer the required data. 16 | 17 | ## Architecture 18 | ![Architecture](https://github.com/AgoraIO/Basic-Recording/blob/master/Agora-Restful-Recording-Nodejs/architecture.png) 19 | 20 | ## Quick Start 21 | ### Background Knowledge 22 | This project presumes you have basic ideas of how Agora Recording SDK works, if not please read [here](https://github.com/AgoraIO/Basic-Recording/tree/master/Agora-LinuxServer-Recording) carefully before start. 23 | 24 | ### Integrate the Agora Recording SDK 25 | 26 | The Agora Recording SDK is not included in the repository. To make everything work, you need to do the following, 27 | 28 | * Download the [Agora Recording SDK for Linux](https://www.agora.io/en/download/). 29 | * Unzip the downloaded SDK package. 30 | * Copy the unziped folder to `record/src/sdk` folder 31 | * Run `npm install -g node-gyp` to install addon build tool 32 | * In terminal change to `record` folder, run `build.sh` (run `build_debug.sh` for debugging purpose), if everything goes smoothly, you should see a file named `agorasdk.node` under `record` folder. 33 | 34 | 35 | ## Run Recording Sample 36 | ### Quick Start 37 | * Change to `record` folder 38 | * Ensure `agorasdk.node` has been properly compiled based on previous steps 39 | * Change the code in `sdkdemo.js`, replace the placeholder with the appid you registered from Agora Official Website 40 | 41 | ``` 42 | fs.mkdir(storageDir, {recursive: true}, err => { 43 | //join channel 44 | rec.joinChannel(null, "agoratest", 0, YOUR_APP_ID, storageDir); 45 | }) 46 | ``` 47 | * Start a Video/Audio conversation using your Agora Video SDK integration with your appid, change the channel name to `agoratest` 48 | * Run `node sdkdemo.js`, and you should see your recording files under `output` folder 49 | 50 | ### Layout Adjustment 51 | The Layout is controlled via function `setMixedLayout`. 52 | In this demo we layout user images from top-left to bottom-right. 53 | 54 | The demo re-layout the images when new user arrives or existing user leaves. 55 | 56 | ``` 57 | rec.on("userjoin", function(uid) { 58 | //rearrange layout when user leaves 59 | console.log(`userleave ${uid}`); 60 | layout.regions = layout.regions.filter(function(region){ 61 | return region.uid !== uid 62 | }) 63 | rec.setMixLayout(layout); 64 | }); 65 | ``` 66 | 67 | ``` 68 | rec.on("userleave", function(uid) { 69 | //rearrange layout when user leaves 70 | console.log(`userleave ${uid}`); 71 | //... adding user to layout 72 | rec.setMixLayout(layout); 73 | }); 74 | ``` 75 | 76 | ## Run Restful Server Sample 77 | ### Quick Start 78 | * Ensure `agorasdk.node` in `record` folder has been properly compiled based on previous steps 79 | * Change to `server` folder 80 | * Run `npm install` to install server dependencies 81 | * Run `node app.js` to start the restful server 82 | 83 | ### Predefined APIs 84 | #### Start Recording 85 | 86 | - `http://localhost:3000/recorder/v1/start` 87 | 88 | Method: 89 | 90 | - POST 91 | 92 | Parameters: 93 | 94 | 95 | |Name|Mandatory|Type|Desc| 96 | |:---- |:---|:----- |----- | 97 | |appid |Y |string |agora appid | 98 | |channel |Y |string | channel name | 99 | |key |N |string | key if certificate is enabled for your appid | 100 | 101 | Example: 102 | ``` 103 | curl -i -X POST -H "Content-type: application/json" --data '{"appid":"XXXXX","channel":"XXXX","key":"XXXX"}' 127.0.0.1:3000/recorder/v1/start 104 | ``` 105 | 106 | Sample Response: 107 | 108 | ``` 109 | { 110 | "success": true, 111 | "sid": "ec8711fb-cd98-4411-a430-a0e8de2c6e98" 112 | } 113 | ``` 114 | 115 | Response Properties: 116 | 117 | |Name|Type|Desc| 118 | |:----|:----- |----- | 119 | |success |bool |operation result | 120 | |sid |string | sid of this operation, need this to stop recorder| 121 | 122 | #### Stop Recording 123 | 124 | - `http://localhost:3000/recorder/v1/stop` 125 | 126 | Method: 127 | 128 | - POST 129 | 130 | Parameters: 131 | 132 | 133 | |Name|Mandatory|Type|Desc| 134 | |:---- |:---|:----- |----- | 135 | |sid |Y |string |sid when you start the recorder | 136 | 137 | Sample Response: 138 | 139 | ``` 140 | { 141 | "success": true 142 | } 143 | ``` 144 | 145 | Response Properties: 146 | 147 | |Name|Type|Desc| 148 | |:----|:----- |----- | 149 | |success |bool |operation result | 150 | 151 | ## Resources 152 | - See full API documentation in the [Document Center](https://docs.agora.io/en/) 153 | - [File bugs about this sample](https://github.com/AgoraIO/Basic-Recording/issues) 154 | - See [detailed Agora Linux Recording guides](https://docs.agora.io/en/2.3.1/addons/Recording/Quickstart%20Guide/recording_cpp?platform=C%2B%2B) 155 | 156 | ## License 157 | This software is licensed under the MIT License (MIT). [View the license](LICENSE.md). 158 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/README_zh.md: -------------------------------------------------------------------------------- 1 | # Agora Restful Recording Nodejs 2 | 3 | *Read this in other languages: [English](README.md)* 4 | 5 | 该文档介绍如何以Restful API的形式快速实现录制。 6 | 7 | ## 准备工作 8 | 请确保满足以下操作系统要求: 9 | - Ubuntu 12.04 x64 或更高版本 或者 CentOS 6.5 x64 或更高版本(推荐CentOS 7) 10 | - GCC 4.4+ 11 | - NodeJS 8.9+ 12 | - 与公网互通,并且能够访问`qos.agoralab.co` 13 | - 每一个录制频道至少1MB+的带宽 14 | 15 | ## 架构 16 | ![架构](https://github.com/AgoraIO/Basic-Recording/blob/master/Agora-Restful-Recording-Nodejs/architecture.png) 17 | 18 | ## 快速开始 19 | ### 背景知识 20 | 该项目依赖于你已经了解了如何使用Agora录制SDK。或者再开始之前阅读[这里](https://github.com/AgoraIO/Basic-Recording/tree/master/Agora-LinuxServer-Recording) 21 | 22 | ### 下载 Agora 录制 SDK 23 | 24 | * 下载[Agora 录制 SDK](https://www.agora.io/en/download/). 25 | * 解压下载的SDK包. 26 | * 把解压后的子文件夹拷贝到目录 `record/src/sdk` 27 | * 运行`npm install -g node-gyp` 安装编译工具 28 | * 切换到目录 `record`, 运行`build.sh` (运行`build_debug.sh`可编译debug版)。如果一切运行顺利,你会在`record`目录内看到`agorasdk.node` 29 | 30 | 31 | ## 运行录制 Sample 32 | ### 快速开始 33 | * 进入目录`record` 34 | * 确保 `agorasdk.node`已经正确的编译 35 | * 更改目录下文件 `sdkdemo.js`, 替换里面的App ID 36 | 37 | ``` 38 | fs.mkdir(storageDir, {recursive: true}, err => { 39 | //join channel 40 | rec.joinChannel(null, "agoratest", 0, YOUR_APP_ID, storageDir); 41 | }) 42 | ``` 43 | * 运行 `node sdkdemo.js`, 你会看到生成的`output`文件夹。该演示Demo仅运行50秒便退出声网频道 44 | 45 | ### 调整布局 46 | 录制的布局通过方法 `setMixedLayout`调整。关于布局,详情参考:[video_mixing_layout](https://docs.agora.io/cn/Recording/API%20Reference/recording_cpp/structagora_1_1linuxsdk_1_1_video_mixing_layout.html) 47 | 48 | 当有用户加入频道或者频道内用户退出时都会更新布局 49 | 50 | ``` 51 | rec.on("userjoin", function(uid) { 52 | //rearrange layout when user leaves 53 | console.log(`userleave ${uid}`); 54 | layout.regions = layout.regions.filter(function(region){ 55 | return region.uid !== uid 56 | }) 57 | rec.setMixLayout(layout); 58 | }); 59 | ``` 60 | 61 | ``` 62 | rec.on("userleave", function(uid) { 63 | //rearrange layout when user leaves 64 | console.log(`userleave ${uid}`); 65 | //... adding user to layout 66 | rec.setMixLayout(layout); 67 | }); 68 | ``` 69 | 70 | ## 运行Restful Server 71 | ### 快速开始 72 | * 确保 `agorasdk.node` 在 `record` 已经被正确编译 73 | * 切换到`server` 目录 74 | * 运行 `npm install`安装依赖库 75 | * 运行 `node app.js` 76 | 77 | ### 预定义 APIs 78 | #### 开始录制 79 | 80 | - `http://localhost:3000/recorder/v1/start` 81 | 82 | 方法: 83 | 84 | - POST 85 | 86 | 参数: 87 | 88 | 89 | |参数名|是否必须|类型|描述| 90 | |:---- |:---|:----- |----- | 91 | |appid |是 |string |Agora AppId | 92 | |channel |是 |string | 频道名 | 93 | |key |否 |string |取决于你是否开启了App ID Certificate | 94 | 95 | 参考: 96 | ``` 97 | curl -i -X POST -H "Content-type: application/json" --data '{"appid":"XXXXX","channel":"XXXX","key":"XXXX"}' 127.0.0.1:3000/recorder/v1/start 98 | ``` 99 | 100 | Sample 应答: 101 | 102 | ``` 103 | { 104 | "success": true, 105 | "sid": "ec8711fb-cd98-4411-a430-a0e8de2c6e98" 106 | } 107 | ``` 108 | 109 | 应答 属性: 110 | 111 | |名称|类型|描述| 112 | |:----|:----- |----- | 113 | |success |bool |执行结果 | 114 | |sid |string | 录制sessionId,用于结束录制 | 115 | 116 | #### 结束录制 117 | 118 | - `http://localhost:3000/recorder/v1/stop` 119 | 120 | 方法: 121 | 122 | - POST 123 | 124 | 参数: 125 | 126 | 127 | |名称|是否必须|类型|描述| 128 | |:---- |:---|:----- |----- | 129 | |sid |是 |string |开启录制时的sessionId | 130 | 131 | Sample 应答: 132 | 133 | ``` 134 | { 135 | "success": true 136 | } 137 | ``` 138 | 139 | 应答 属性: 140 | 141 | |名称|类型|描述| 142 | |:----|:----- |----- | 143 | |success |bool |执行结果 | 144 | 145 | ## 联系我们 146 | - 完整的 API 文档见 [文档中心](https://docs.agora.io/en/) 147 | - 如果在集成中遇到问题, 你可以到[开发者社区](https://github.com/AgoraIO/Basic-Recording/issues)提问 148 | - 详情请参考 [录制快速入门](https://docs.agora.io/en/2.3.1/addons/Recording/Quickstart%20Guide/recording_cpp?platform=C%2B%2B) 149 | 150 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgoraIO/Basic-Recording/35b199145db4d9bf75dc1c8ececd9c1bdb089e4c/On-Premise-Recording-Nodejs/architecture.png -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/AgoraRecordSdk.js: -------------------------------------------------------------------------------- 1 | const agora = require("./agorasdk"); 2 | const path = require("path"); 3 | const fs = require("fs") 4 | const EventEmitter = require("events") 5 | 6 | class AgoraRecordSdk extends EventEmitter { 7 | constructor() { 8 | super(); 9 | this.recording = new agora.NodeRecordingSdk(); 10 | this.initEventHandler(); 11 | } 12 | 13 | initEventHandler() { 14 | let self = this; 15 | 16 | let fire = (...args) => { 17 | setImmediate(() => { 18 | this.emit(...args) 19 | }) 20 | } 21 | 22 | this.onEvent("joinchannel", (channel, uid) => { 23 | fire('joinchannel', channel, uid); 24 | }); 25 | this.onEvent("error", (err, stat) => { 26 | fire('error', err, stat); 27 | }); 28 | this.onEvent("userjoin", (err, stat) => { 29 | fire('userjoin', err, stat); 30 | }); 31 | this.onEvent("userleave", (err, stat) => { 32 | fire('userleave', err, stat); 33 | }); 34 | this.onEvent("activespeaker", uid => { 35 | fire("activespeaker", uid) 36 | }); 37 | this.onEvent("connectionlost", () => { 38 | fire("connectionlost") 39 | }) 40 | this.onEvent("connectioninterrupt", () => { 41 | fire("connectioninterrupt") 42 | }) 43 | this.onEvent("receivingstreamstatuschanged", (receivingAudio, receivingVideo) => { 44 | fire("receivingstreamstatuschanged", receivingAudio, receivingVideo) 45 | }) 46 | this.onEvent("firstremotevideodecoded", (uid, width, height, elapsed) => { 47 | fire("firstremotevideodecoded", uid, width, height, elapsed) 48 | }) 49 | this.onEvent("firstremoteaudioframe", (uid, elapsed) => { 50 | fire("firstremoteaudioframe", uid, elapsed) 51 | }) 52 | this.onEvent("audiovolumeindication", (speakers, speakerNum) => { 53 | fire("audiovolumeindication", speakers, speakerNum) 54 | }) 55 | this.onEvent("remoteVideoStreamStateChanged", (uid, state, reason) => { 56 | fire("remoteVideoStreamStateChanged", uid, state, reason) 57 | }) 58 | this.onEvent("remoteAudioStreamStateChanged", (uid, state, reason) => { 59 | fire("remoteAudioStreamStateChanged", uid, state, reason) 60 | }) 61 | this.onEvent("rejoinChannelSuccess", (channel, uid) => { 62 | fire("rejoinChannelSuccess", channel, uid) 63 | }) 64 | this.onEvent("connectionStateChanged", (state, reason) => { 65 | fire("connectionStateChanged", state, reason) 66 | }) 67 | this.onEvent("remoteVideoStats", (uid, stats) => { 68 | fire("remoteVideoStats", uid, stats) 69 | }) 70 | this.onEvent("remoteAudioStats", (uid, stats) => { 71 | fire("remoteAudioStats", uid, stats) 72 | }) 73 | this.onEvent("recordingStats", (stats) => { 74 | fire("recordingStats", stats) 75 | }) 76 | this.onEvent("localUserRegistered", (uid, account) => { 77 | fire("localUserRegistered", uid, account) 78 | }) 79 | this.onEvent("userInfoUpdated", (uid, info) => { 80 | fire("userInfoUpdated", uid, info) 81 | }) 82 | } 83 | 84 | joinChannel(key, name, uid, appid, storeFolder) { 85 | return new Promise((resolve, reject) => { 86 | let binPath = path.join(__dirname, "./src/sdk/bin/"); 87 | fs.access(storeFolder, fs.constants.W_OK, (err) => { 88 | if(err) { 89 | throw "folder not writable" 90 | } 91 | const json = { 92 | Recording_Dir: `${storeFolder}` 93 | }; 94 | const cfgPath = path.join(storeFolder, '/cfg.json') 95 | fs.writeFile(cfgPath, JSON.stringify(json), err => { 96 | this.recording.joinChannel(key || null, name, binPath, appid, uid, cfgPath); 97 | this.once("error", err => { 98 | reject(err); 99 | }) 100 | this.once("joinchannel", () => { 101 | resolve(); 102 | }); 103 | }); 104 | }); 105 | 106 | }); 107 | } 108 | 109 | setMixLayout(layout) { 110 | return this.recording.setMixLayout(layout); 111 | } 112 | 113 | onEvent(event, cb) { 114 | return this.recording.onEvent(event, cb); 115 | }; 116 | 117 | leaveChannel() { 118 | return this.recording.leaveChannel(); 119 | } 120 | 121 | release() { 122 | return this.recording.release(); 123 | } 124 | } 125 | 126 | module.exports = AgoraRecordSdk; -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/build.sh: -------------------------------------------------------------------------------- 1 | cd src 2 | node-gyp clean 3 | node-gyp configure 4 | node-gyp build 5 | cp build/Release/agorasdk.node ../. -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/build_debug.sh: -------------------------------------------------------------------------------- 1 | cd src 2 | node-gyp clean 3 | node-gyp configure --debug 4 | node-gyp build 5 | cp build/Debug/agorasdk.node ../. -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/sdkdemo.js: -------------------------------------------------------------------------------- 1 | const AgoraRecordingSDK = require("./AgoraRecordSdk"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | function startRecording() { 5 | let rec = new AgoraRecordingSDK(); 6 | let layout = { 7 | "canvasWidth": 640, 8 | "canvasHeight": 480, 9 | "backgroundColor": "#00ff00", 10 | "regions": [] 11 | } 12 | rec.setMixLayout(layout); 13 | rec.on("joinchannel", function (channel, uid) { 14 | console.log(`channel joined ${channel} ${uid}`); 15 | }); 16 | rec.on("error", function (err, stat) { 17 | console.log(`err ${err} ${stat}`); 18 | }); 19 | rec.on("recordingStats", function(stats) { 20 | console.log(`recording stats ${JSON.stringify(stats)}`) 21 | }) 22 | rec.on("userleave", function(uid) { 23 | //rearrange layout when user leaves 24 | console.log(`userleave ${uid}`); 25 | layout.regions = layout.regions.filter(function(region){ 26 | return region.uid !== uid 27 | }) 28 | rec.setMixLayout(layout); 29 | }); 30 | rec.on("userjoin", function (uid) { 31 | //rearrange layout when new user joins 32 | console.log(`userjoin ${uid}`); 33 | let region = { 34 | "x": 0, 35 | "y": 0, 36 | "width": 320, 37 | "height": 240, 38 | "zOrder": 1, 39 | "alpha": 1, 40 | "uid": uid 41 | } 42 | switch(layout.regions.length) { 43 | case 0: 44 | region.x = 0; 45 | region.y = 0; 46 | break; 47 | case 1: 48 | region.x = 320; 49 | region.y = 0; 50 | break; 51 | case 2: 52 | region.x = 0; 53 | region.y = 240; 54 | break; 55 | case 3: 56 | region.x = 320; 57 | region.y = 240; 58 | default: 59 | break; 60 | } 61 | layout.regions.push(region) 62 | rec.setMixLayout(layout); 63 | }); 64 | let storageDir = path.resolve(__dirname, `./output`); 65 | 66 | //create output folder 67 | fs.mkdir(storageDir, {recursive: true}, err => { 68 | //join channel 69 | rec.joinChannel(null, "agoratest", 0, "", storageDir); 70 | }) 71 | return rec; 72 | } 73 | 74 | 75 | let recorder = startRecording(); 76 | 77 | setTimeout(() => { 78 | recorder.leaveChannel(); 79 | }, 1000 * 50) 80 | // setInterval(() => { 81 | // //prevent from exiting 82 | // }, 1000 * 30); 83 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/README.md: -------------------------------------------------------------------------------- 1 | # Analysis and Workaround 2 | 3 | If we run the `node-gyp rebuild` command manually to build for node.js, we see that the `common.gypi` file used by node-gyp when building for node (7.1.0 on my system) is `~/.node-gyp/7.1.0/include/node/common.gypi 4 | ` whereas when we build for electron, we are using `~/.electron-gyp/.node-gyp/iojs-1.4.14/common.gypi`. 5 | 6 | ## Workaround 7 | 8 | We can get this to compile if we add the following fragment to `binding.gyp` in this project: 9 | This should be added as a property of the object in the `targets` array, so that the complete `binding.gyp` becomes: 10 | 11 | ``` 12 | { 13 | "targets": [ 14 | { 15 | "target_name": "hello", 16 | "sources": [ "hello.cc" ], 17 | "include_dirs": [ 18 | " 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "IAgoraLinuxSdkCommon.h" 10 | #include "IAgoraRecordingEngine.h" 11 | #include "AgoraSdk.h" 12 | #include "node_async_queue.h" 13 | 14 | #include "base/atomic.h" 15 | #include "opt_parser.h" 16 | namespace agora { 17 | 18 | #define MAKE_JS_CALL_0(ev) \ 19 | auto it = m_callbacks.find(ev); \ 20 | if (it != m_callbacks.end()) {\ 21 | Isolate *isolate = Isolate::GetCurrent();\ 22 | HandleScope scope(isolate);\ 23 | NodeEventCallback& cb = *it->second;\ 24 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 0, nullptr);\ 25 | } 26 | 27 | #define MAKE_JS_CALL_1(ev, type, param) \ 28 | auto it = m_callbacks.find(ev); \ 29 | if (it != m_callbacks.end()) {\ 30 | Isolate *isolate = Isolate::GetCurrent();\ 31 | HandleScope scope(isolate);\ 32 | Local argv[1]{ napi_create_##type##_(isolate, param)\ 33 | };\ 34 | NodeEventCallback& cb = *it->second;\ 35 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 1, argv);\ 36 | } 37 | 38 | #define MAKE_JS_CALL_2(ev, type1, param1, type2, param2) \ 39 | auto it = m_callbacks.find(ev); \ 40 | if (it != m_callbacks.end()) {\ 41 | Isolate *isolate = Isolate::GetCurrent();\ 42 | HandleScope scope(isolate);\ 43 | Local argv[2]{ napi_create_##type1##_(isolate, param1),\ 44 | napi_create_##type2##_(isolate, param2)\ 45 | };\ 46 | NodeEventCallback& cb = *it->second;\ 47 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 2, argv);\ 48 | } 49 | 50 | #define MAKE_JS_CALL_3(ev, type1, param1, type2, param2, type3, param3) \ 51 | auto it = m_callbacks.find(ev); \ 52 | if (it != m_callbacks.end()) {\ 53 | Isolate *isolate = Isolate::GetCurrent();\ 54 | HandleScope scope(isolate);\ 55 | Local argv[3]{ napi_create_##type1##_(isolate, param1),\ 56 | napi_create_##type2##_(isolate, param2),\ 57 | napi_create_##type3##_(isolate, param3) \ 58 | };\ 59 | NodeEventCallback& cb = *it->second;\ 60 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 3, argv);\ 61 | } 62 | 63 | #define MAKE_JS_CALL_4(ev, type1, param1, type2, param2, type3, param3, type4, param4) \ 64 | auto it = m_callbacks.find(ev); \ 65 | if (it != m_callbacks.end()) {\ 66 | Isolate *isolate = Isolate::GetCurrent();\ 67 | HandleScope scope(isolate);\ 68 | Local argv[4]{ napi_create_##type1##_(isolate, param1),\ 69 | napi_create_##type2##_(isolate, param2),\ 70 | napi_create_##type3##_(isolate, param3), \ 71 | napi_create_##type4##_(isolate, param4), \ 72 | };\ 73 | NodeEventCallback& cb = *it->second;\ 74 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 4, argv);\ 75 | } 76 | 77 | #define MAKE_JS_CALL_5(ev, type1, param1, type2, param2, type3, param3, type4, param4, type5, param5) \ 78 | auto it = m_callbacks.find(ev); \ 79 | if (it != m_callbacks.end()) {\ 80 | Isolate *isolate = Isolate::GetCurrent();\ 81 | HandleScope scope(isolate);\ 82 | Local argv[5]{ napi_create_##type1##_(isolate, param1),\ 83 | napi_create_##type2##_(isolate, param2),\ 84 | napi_create_##type3##_(isolate, param3), \ 85 | napi_create_##type4##_(isolate, param4), \ 86 | napi_create_##type5##_(isolate, param5), \ 87 | };\ 88 | NodeEventCallback& cb = *it->second;\ 89 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 5, argv);\ 90 | } 91 | 92 | 93 | #define NODE_SET_OBJ_PROP_UINT32(obj, name, val) \ 94 | { \ 95 | Local propName = String::NewFromUtf8(isolate, name, NewStringType::kInternalized).ToLocalChecked(); \ 96 | Local propVal = napi_create_uint32_(isolate, val); \ 97 | v8::Maybe ret = obj->Set(isolate->GetCurrentContext(), propName, propVal); \ 98 | if(!ret.IsNothing()) { \ 99 | if(!ret.ToChecked()) { \ 100 | break; \ 101 | } \ 102 | } \ 103 | } 104 | #define NODE_SET_OBJ_PROP_UID(obj, name, val) \ 105 | { \ 106 | Local propName = String::NewFromUtf8(isolate, name, NewStringType::kInternalized).ToLocalChecked(); \ 107 | Local propVal = NodeUid::getNodeValue(isolate, val); \ 108 | v8::Maybe ret = obj->Set(isolate->GetCurrentContext(), propName, propVal); \ 109 | if(!ret.IsNothing()) { \ 110 | if(!ret.ToChecked()) { \ 111 | break; \ 112 | } \ 113 | } \ 114 | } 115 | #define NODE_SET_OBJ_PROP_STRING(obj, name, val) \ 116 | { \ 117 | Local propName = String::NewFromUtf8(isolate, name, NewStringType::kInternalized).ToLocalChecked(); \ 118 | Local propVal = String::NewFromUtf8(isolate, val, NewStringType::kInternalized).ToLocalChecked(); \ 119 | v8::Maybe ret = obj->Set(isolate->GetCurrentContext(), propName, propVal); \ 120 | if(!ret.IsNothing()) { \ 121 | if(!ret.ToChecked()) { \ 122 | break; \ 123 | } \ 124 | } \ 125 | } 126 | 127 | AgoraSdk::AgoraSdk(): IRecordingEngineEventHandler() { 128 | m_engine = NULL; 129 | m_stopped.store(false); 130 | m_storage_dir = "./"; 131 | } 132 | 133 | AgoraSdk::~AgoraSdk() { 134 | if (m_engine) { 135 | m_engine->leaveChannel(); 136 | m_engine->release(); 137 | } 138 | } 139 | 140 | bool AgoraSdk::stopped() const { 141 | return m_stopped; 142 | } 143 | 144 | bool AgoraSdk::release() { 145 | if (m_engine) { 146 | m_engine->release(); 147 | m_engine = NULL; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | bool AgoraSdk::createChannel(const string &appid, const string &channelKey, const string &name, 154 | uint32_t uid, 155 | agora::recording::RecordingConfig &config) 156 | { 157 | if ((m_engine = agora::recording::IRecordingEngine::createAgoraRecordingEngine(appid.c_str(), this)) == NULL) 158 | return false; 159 | 160 | if(linuxsdk::ERR_OK != m_engine->joinChannel(channelKey.c_str(), name.c_str(), uid, config)) 161 | return false; 162 | 163 | m_config = config; 164 | return true; 165 | } 166 | 167 | bool AgoraSdk::leaveChannel() { 168 | if (m_engine) { 169 | m_engine->leaveChannel(); 170 | m_stopped = true; 171 | } 172 | 173 | return true; 174 | } 175 | 176 | int AgoraSdk::startService() { 177 | if (m_engine) 178 | return m_engine->startService(); 179 | 180 | return 1; 181 | } 182 | 183 | int AgoraSdk::stopService() { 184 | if (m_engine) 185 | return m_engine->stopService(); 186 | 187 | return 1; 188 | } 189 | 190 | void AgoraSdk::updateMixLayout(agora::linuxsdk::VideoMixingLayout &layout) { 191 | m_layout = layout; 192 | setVideoMixLayout(); 193 | } 194 | 195 | agora::linuxsdk::VideoMixingLayout AgoraSdk::getMixLayout() { 196 | return m_layout; 197 | } 198 | 199 | //Customize the layout of video under video mixing model 200 | int AgoraSdk::setVideoMixLayout() 201 | { 202 | // recording::RecordingConfig *pConfig = getConfigInfo(); 203 | // size_t max_peers = pConfig->channelProfile == linuxsdk::CHANNEL_PROFILE_COMMUNICATION ? 7:17; 204 | if(m_layout.regionCount == 0) return 0; 205 | 206 | cout << "color: " << m_layout.backgroundColor << endl; 207 | return setVideoMixingLayout(m_layout); 208 | } 209 | 210 | int AgoraSdk::setVideoMixingLayout(const agora::linuxsdk::VideoMixingLayout &layout) 211 | { 212 | int result = -agora::linuxsdk::ERR_INTERNAL_FAILED; 213 | if(m_engine) 214 | result = m_engine->setVideoMixingLayout(layout); 215 | return result; 216 | } 217 | 218 | const agora::recording::RecordingEngineProperties* AgoraSdk::getRecorderProperties(){ 219 | return m_engine->getProperties(); 220 | } 221 | 222 | void AgoraSdk::onErrorImpl(int error, agora::linuxsdk::STAT_CODE_TYPE stat_code) { 223 | cerr << "Error: " << error <<",with stat_code:"<< stat_code << endl; 224 | m_engine->stoppedOnError(); 225 | agora::recording::node_async_call::async_call([this, error, stat_code]() { 226 | MAKE_JS_CALL_2(REC_EVENT_ERROR, int32, error, int32, stat_code); 227 | }); 228 | } 229 | 230 | void AgoraSdk::onWarningImpl(int warn) { 231 | cerr << "warn: " << warn << endl; 232 | // leaveChannel(); 233 | } 234 | 235 | void AgoraSdk::onJoinChannelSuccessImpl(const char * channelId, agora::linuxsdk::uid_t uid) { 236 | cout << "join channel Id: " << channelId << ", with uid: " << uid << endl; 237 | string channelName = channelId; 238 | agora::recording::node_async_call::async_call([this, channelName, uid]() { 239 | MAKE_JS_CALL_2(REC_EVENT_JOIN_CHANNEL, string, channelName.c_str(), uid, uid); 240 | }); 241 | } 242 | 243 | void AgoraSdk::onLeaveChannelImpl(agora::linuxsdk::LEAVE_PATH_CODE code) { 244 | cout << "leave channel with code:" << code << endl; 245 | agora::recording::node_async_call::async_call([this]() { 246 | MAKE_JS_CALL_0(REC_EVENT_LEAVE_CHANNEL); 247 | }); 248 | } 249 | 250 | void AgoraSdk::onUserJoinedImpl(unsigned uid, agora::linuxsdk::UserJoinInfos &infos) { 251 | cout << "User " << uid << " joined, RecordingDir:" << (infos.storageDir? infos.storageDir:"NULL") <type == agora::linuxsdk::AUDIO_FRAME_RAW_PCM) { 296 | info_name += ".pcm"; 297 | 298 | agora::linuxsdk::AudioPcmFrame *f = pframe->frame.pcm; 299 | data = f->pcmBuf_; 300 | size = f->pcmBufSize_; 301 | 302 | cout << "User " << uid << ", received a raw PCM frame ,channels:" << f->channels_ <type == agora::linuxsdk::AUDIO_FRAME_AAC) { 305 | info_name += ".aac"; 306 | 307 | cout << "User " << uid << ", received an AAC frame" << endl; 308 | 309 | agora::linuxsdk::AudioAacFrame *f = pframe->frame.aac; 310 | data = f->aacBuf_; 311 | size = f->aacBufSize_; 312 | } 313 | 314 | FILE *fp = fopen(info_name.c_str(), "a+b"); 315 | if(fp == NULL) { 316 | cout << "failed to open: " << info_name; 317 | cout<< " "; 318 | cout << "errno: " << errno; 319 | cout<< endl; 320 | return; 321 | } 322 | 323 | ::fwrite(data, 1, size, fp); 324 | ::fclose(fp); 325 | } 326 | 327 | void AgoraSdk::videoFrameReceivedImpl(unsigned int uid, const agora::linuxsdk::VideoFrame *pframe) const { 328 | char uidbuf[65]; 329 | snprintf(uidbuf, sizeof(uidbuf),"%u", uid); 330 | const char * suffix=".vtmp"; 331 | if (pframe->type == agora::linuxsdk::VIDEO_FRAME_RAW_YUV) { 332 | agora::linuxsdk::VideoYuvFrame *f = pframe->frame.yuv; 333 | suffix=".yuv"; 334 | 335 | cout << "User " << uid << ", received a yuv frame, width: " 336 | << f->width_ << ", height: " << f->height_ ; 337 | cout<<",ystride:"<ystride_<< ",ustride:"<ustride_<<",vstride:"<vstride_; 338 | cout<< endl; 339 | } else if(pframe->type == agora::linuxsdk::VIDEO_FRAME_JPG) { 340 | suffix=".jpg"; 341 | agora::linuxsdk::VideoJpgFrame *f = pframe->frame.jpg; 342 | 343 | cout << "User " << uid << ", received an jpg frame, timestamp: " 344 | << f->frame_ms_ << endl; 345 | 346 | struct tm date; 347 | time_t t = time(NULL); 348 | localtime_r(&t, &date); 349 | char timebuf[128]; 350 | sprintf(timebuf, "%04d%02d%02d%02d%02d%02d", date.tm_year + 1900, date.tm_mon + 1, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec); 351 | std::string file_name = m_storage_dir + std::string(uidbuf) + "_" + std::string(timebuf) + suffix; 352 | FILE *fp = fopen(file_name.c_str(), "w"); 353 | if(fp == NULL) { 354 | cout << "failed to open: " << file_name; 355 | cout<< " "; 356 | cout << "errno: " << errno; 357 | cout<< endl; 358 | return; 359 | } 360 | 361 | ::fwrite(f->buf_, 1, f->bufSize_, fp); 362 | ::fclose(fp); 363 | return; 364 | } else { 365 | suffix=".h264"; 366 | agora::linuxsdk::VideoH264Frame *f = pframe->frame.h264; 367 | 368 | cout << "User " << uid << ", received an h264 frame, timestamp: " 369 | << f->frame_ms_ << ", frame no: " << f->frame_num_ << endl; 370 | } 371 | 372 | std::string info_name = m_storage_dir + std::string(uidbuf) /*+ timestamp_per_join_ */+ std::string(suffix); 373 | FILE *fp = fopen(info_name.c_str(), "a+b"); 374 | if(fp == NULL) { 375 | cout << "failed to open: " << info_name; 376 | cout<< " "; 377 | cout << "errno: " << errno; 378 | cout<< endl; 379 | return; 380 | } 381 | 382 | 383 | //store it as file 384 | if (pframe->type == agora::linuxsdk::VIDEO_FRAME_RAW_YUV) { 385 | agora::linuxsdk::VideoYuvFrame *f = pframe->frame.yuv; 386 | ::fwrite(f->buf_, 1, f->bufSize_, fp); 387 | } 388 | else { 389 | agora::linuxsdk::VideoH264Frame *f = pframe->frame.h264; 390 | ::fwrite(f->buf_, 1, f->bufSize_, fp); 391 | } 392 | ::fclose(fp); 393 | 394 | } 395 | 396 | void AgoraSdk::addEventHandler(const std::string& eventName, Persistent& obj, Persistent& callback) 397 | { 398 | NodeEventCallback *cb = new NodeEventCallback();; 399 | cb->js_this.Reset(Isolate::GetCurrent(), obj); 400 | cb->callback.Reset(Isolate::GetCurrent(), callback); 401 | m_callbacks.emplace(eventName, cb); 402 | } 403 | 404 | void AgoraSdk::emitError(int err, int stat_code) { 405 | agora::recording::node_async_call::async_call([this, err, stat_code]() { 406 | MAKE_JS_CALL_2(REC_EVENT_ERROR, int32, err, int32, stat_code); 407 | }); 408 | } 409 | 410 | //added 2.3.3 411 | void AgoraSdk::onAudioVolumeIndication_node(const agora::linuxsdk::AudioVolumeInfo* speakers, unsigned int speakerNumber) { 412 | auto it = m_callbacks.find(RTC_EVENT_AUDIO_VOLUME_INDICATION); 413 | if (it != m_callbacks.end()) { 414 | Isolate *isolate = Isolate::GetCurrent(); 415 | HandleScope scope(isolate); 416 | Local arrSpeakers = v8::Array::New(isolate, speakerNumber); 417 | for(int i = 0; i < speakerNumber; i++) { 418 | Local obj = Object::New(isolate); 419 | obj->Set(napi_create_string_(isolate, "uid"), napi_create_uid_(isolate, speakers[i].uid)); 420 | obj->Set(napi_create_string_(isolate, "volume"), napi_create_uint32_(isolate, speakers[i].volume)); 421 | arrSpeakers->Set(i, obj); 422 | } 423 | 424 | Local argv[2]{ arrSpeakers, 425 | napi_create_uint32_(isolate, speakerNumber) 426 | }; 427 | NodeEventCallback& cb = *it->second; 428 | cb.callback.Get(isolate)->Call(cb.js_this.Get(isolate), 2, argv); 429 | } 430 | } 431 | 432 | void AgoraSdk::onAudioVolumeIndication(const agora::linuxsdk::AudioVolumeInfo* speakers, unsigned int speakerNum){ 433 | if (speakers) { 434 | agora::linuxsdk::AudioVolumeInfo* localSpeakers = new agora::linuxsdk::AudioVolumeInfo[speakerNum]; 435 | for(int i = 0; i < speakerNum; i++) { 436 | agora::linuxsdk::AudioVolumeInfo tmp = speakers[i]; 437 | localSpeakers[i].uid = tmp.uid; 438 | localSpeakers[i].volume = tmp.volume; 439 | } 440 | agora::recording::node_async_call::async_call([this, localSpeakers, speakerNum] { 441 | this->onAudioVolumeIndication_node(localSpeakers, speakerNum); 442 | delete []localSpeakers; 443 | }); 444 | } 445 | } 446 | 447 | void AgoraSdk::onFirstRemoteVideoDecoded(uid_t uid, int width, int height, int elapsed) { 448 | agora::recording::node_async_call::async_call([this, uid, width, height, elapsed]() { 449 | MAKE_JS_CALL_4(REC_EVENT_FIRST_VIDEO_FRAME, uid, uid, int32, width, int32, height, int32, elapsed); 450 | }); 451 | } 452 | 453 | void AgoraSdk::onFirstRemoteAudioFrame(uid_t uid, int elapsed) { 454 | agora::recording::node_async_call::async_call([this, uid, elapsed]() { 455 | MAKE_JS_CALL_2(REC_EVENT_FIRST_AUDIO_FRAME, uid, uid, int32, elapsed); 456 | }); 457 | } 458 | 459 | void AgoraSdk::onReceivingStreamStatusChanged(bool receivingAudio, bool receivingVideo) { 460 | agora::recording::node_async_call::async_call([this, receivingAudio, receivingVideo]() { 461 | MAKE_JS_CALL_2(REC_EVENT_STREAM_CHANGED, bool, receivingAudio, bool, receivingVideo); 462 | }); 463 | } 464 | 465 | void AgoraSdk::onConnectionLost() { 466 | agora::recording::node_async_call::async_call([this]() { 467 | MAKE_JS_CALL_0(REC_EVENT_CONN_LOST); 468 | }); 469 | } 470 | 471 | void AgoraSdk::onConnectionInterrupted() { 472 | agora::recording::node_async_call::async_call([this]() { 473 | MAKE_JS_CALL_0(REC_EVENT_CONN_INTER); 474 | }); 475 | } 476 | 477 | void AgoraSdk::onRemoteVideoStreamStateChanged(uid_t uid, linuxsdk::RemoteStreamState state, linuxsdk::RemoteStreamStateChangedReason reason) { 478 | agora::recording::node_async_call::async_call([this, uid, state, reason]() { 479 | MAKE_JS_CALL_3(REC_EVENT_REMOTE_VIDEO_STREAM_STATE_CHANGED, uid, uid, int32, state, int32, reason); 480 | }); 481 | } 482 | 483 | void AgoraSdk::onRemoteAudioStreamStateChanged(uid_t uid, agora::linuxsdk::RemoteStreamState state, agora::linuxsdk::RemoteStreamStateChangedReason reason) { 484 | agora::recording::node_async_call::async_call([this, uid, state, reason]() { 485 | MAKE_JS_CALL_3(REC_EVENT_REMOTE_AUDIO_STREAM_STATE_CHANGED, uid, uid, int32, state, int32, reason); 486 | }); 487 | } 488 | 489 | void AgoraSdk::onRejoinChannelSuccess(const char* channelId, uid_t uid) { 490 | std::string sChannelId(channelId); 491 | agora::recording::node_async_call::async_call([this, sChannelId, uid]() { 492 | MAKE_JS_CALL_2(REC_EVENT_REJOIN_SUCCESS, string, sChannelId.c_str(), uid, uid); 493 | }); 494 | } 495 | 496 | void AgoraSdk::onConnectionStateChanged(agora::linuxsdk::ConnectionStateType state, agora::linuxsdk::ConnectionChangedReasonType reason) { 497 | agora::recording::node_async_call::async_call([this, state, reason]() { 498 | MAKE_JS_CALL_2(REC_EVENT_CONN_STATE_CHANGED, int32, state, int32, reason); 499 | }); 500 | } 501 | void AgoraSdk::onRemoteVideoStats(agora::linuxsdk::uid_t uid, const agora::linuxsdk::RemoteVideoStats& stats) { 502 | agora::recording::node_async_call::async_call([this, uid, stats]() { 503 | do { 504 | Isolate *isolate = Isolate::GetCurrent(); 505 | HandleScope scope(isolate); 506 | Local context = isolate->GetCurrentContext(); 507 | Local obj = Object::New(isolate); 508 | 509 | NODE_SET_OBJ_PROP_UINT32(obj, "delay", stats.delay); 510 | NODE_SET_OBJ_PROP_UINT32(obj, "width", stats.width); 511 | NODE_SET_OBJ_PROP_UINT32(obj, "height", stats.height); 512 | NODE_SET_OBJ_PROP_UINT32(obj, "receivedBitrate", stats.receivedBitrate); 513 | NODE_SET_OBJ_PROP_UINT32(obj, "decoderOutputFrameRate", stats.decoderOutputFrameRate); 514 | NODE_SET_OBJ_PROP_UINT32(obj, "rxStreamType", stats.rxStreamType); 515 | Local arg[2] = { napi_create_uid_(isolate, uid), obj }; 516 | auto it = m_callbacks.find(REC_EVENT_REMOTE_VIDEO_STATS); 517 | if (it != m_callbacks.end()) { 518 | it->second->callback.Get(isolate)->Call(context, it->second->js_this.Get(isolate), 2, arg); \ 519 | } 520 | } while(false); 521 | }); 522 | } 523 | 524 | void AgoraSdk::onRemoteAudioStats(agora::linuxsdk::uid_t uid, const agora::linuxsdk::RemoteAudioStats& stats) { 525 | agora::recording::node_async_call::async_call([this, uid, stats]() { 526 | do { 527 | Isolate *isolate = Isolate::GetCurrent(); 528 | HandleScope scope(isolate); 529 | Local context = isolate->GetCurrentContext(); 530 | Local obj = Object::New(isolate); 531 | 532 | NODE_SET_OBJ_PROP_UINT32(obj, "quality", stats.quality); 533 | NODE_SET_OBJ_PROP_UINT32(obj, "networkTransportDelay", stats.networkTransportDelay); 534 | NODE_SET_OBJ_PROP_UINT32(obj, "jitterBufferDelay", stats.jitterBufferDelay); 535 | NODE_SET_OBJ_PROP_UINT32(obj, "audioLossRate", stats.audioLossRate); 536 | Local arg[2] = { napi_create_uid_(isolate, uid), obj }; 537 | auto it = m_callbacks.find(REC_EVENT_REMOTE_AUDIO_STATS); 538 | if (it != m_callbacks.end()) { 539 | it->second->callback.Get(isolate)->Call(context, it->second->js_this.Get(isolate), 2, arg); \ 540 | } 541 | } while(false); 542 | }); 543 | } 544 | 545 | void AgoraSdk::onRecordingStats(const agora::linuxsdk::RecordingStats& stats) { 546 | agora::recording::node_async_call::async_call([this, stats]() { 547 | do { 548 | Isolate *isolate = Isolate::GetCurrent(); 549 | HandleScope scope(isolate); 550 | Local context = isolate->GetCurrentContext(); 551 | Local obj = Object::New(isolate); 552 | 553 | NODE_SET_OBJ_PROP_UINT32(obj, "duration", stats.duration); 554 | NODE_SET_OBJ_PROP_UINT32(obj, "rxBytes", stats.rxBytes); 555 | NODE_SET_OBJ_PROP_UINT32(obj, "rxKBitRate", stats.rxKBitRate); 556 | NODE_SET_OBJ_PROP_UINT32(obj, "rxAudioKBitRate", stats.rxAudioKBitRate); 557 | NODE_SET_OBJ_PROP_UINT32(obj, "rxVideoKBitRate", stats.rxVideoKBitRate); 558 | NODE_SET_OBJ_PROP_UINT32(obj, "lastmileDelay", stats.lastmileDelay); 559 | NODE_SET_OBJ_PROP_UINT32(obj, "userCount", stats.userCount); 560 | NODE_SET_OBJ_PROP_UINT32(obj, "cpuAppUsage", stats.cpuAppUsage); 561 | NODE_SET_OBJ_PROP_UINT32(obj, "cpuTotalUsage", stats.cpuTotalUsage); 562 | Local arg[1] = { obj }; 563 | auto it = m_callbacks.find(REC_EVENT_RECORDING_STATS); 564 | if (it != m_callbacks.end()) { 565 | it->second->callback.Get(isolate)->Call(context, it->second->js_this.Get(isolate), 1, arg); \ 566 | } 567 | } while(false); 568 | }); 569 | } 570 | 571 | void AgoraSdk::onLocalUserRegistered(uid_t uid, const char* userAccount) { 572 | std::string sUserAccount(userAccount); 573 | agora::recording::node_async_call::async_call([this, uid, sUserAccount]() { 574 | MAKE_JS_CALL_2(REC_EVENT_LOCAL_USER_REGISTER, uid, uid, string, sUserAccount.c_str()); 575 | }); 576 | } 577 | 578 | void AgoraSdk::onUserInfoUpdated(uid_t uid, const agora::linuxsdk::UserInfo& info) { 579 | agora::recording::node_async_call::async_call([this, uid, info]() { 580 | do { 581 | Isolate *isolate = Isolate::GetCurrent(); 582 | HandleScope scope(isolate); 583 | Local context = isolate->GetCurrentContext(); 584 | Local obj = Object::New(isolate); 585 | 586 | NODE_SET_OBJ_PROP_UID(obj, "uid", info.uid); 587 | NODE_SET_OBJ_PROP_STRING(obj, "userAccount", info.userAccount); 588 | 589 | Local arg[2] = { 590 | napi_create_uid_(isolate, uid), 591 | obj 592 | }; 593 | auto it = m_callbacks.find(REC_EVENT_USER_INFO_UPDATED); 594 | if (it != m_callbacks.end()) { 595 | it->second->callback.Get(isolate)->Call(context, it->second->js_this.Get(isolate), 2, arg); \ 596 | } 597 | } while(false); 598 | }); 599 | } 600 | 601 | } -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/AgoraSdk.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "node_napi_api.h" 9 | #include "node_uid.h" 10 | 11 | #include "IAgoraLinuxSdkCommon.h" 12 | #include "IAgoraRecordingEngine.h" 13 | 14 | #include "base/atomic.h" 15 | #include "opt_parser.h" 16 | 17 | namespace agora { 18 | 19 | using std::string; 20 | using std::cout; 21 | using std::cerr; 22 | using std::endl; 23 | 24 | using agora::base::opt_parser; 25 | using agora::linuxsdk::VideoFrame; 26 | using agora::linuxsdk::AudioFrame; 27 | 28 | 29 | struct MixModeSettings { 30 | int m_height; 31 | int m_width; 32 | bool m_videoMix; 33 | MixModeSettings(): 34 | m_height(0), 35 | m_width(0), 36 | m_videoMix(false) 37 | {}; 38 | }; 39 | 40 | 41 | class AgoraSdk : virtual public agora::recording::IRecordingEngineEventHandler { 42 | #define REC_EVENT_JOIN_CHANNEL "joinchannel" 43 | #define REC_EVENT_LEAVE_CHANNEL "leavechannel" 44 | #define REC_EVENT_ERROR "error" 45 | #define REC_EVENT_USER_JOIN "userjoin" 46 | #define REC_EVENT_USER_LEAVE "userleave" 47 | #define REC_EVENT_ACTIVE_SPEAKER "activespeaker" 48 | #define REC_EVENT_CONN_LOST "connectionlost" 49 | #define REC_EVENT_CONN_INTER "connectioninterrupt" 50 | #define REC_EVENT_STREAM_CHANGED "receivingstreamstatuschanged" 51 | #define REC_EVENT_FIRST_VIDEO_FRAME "firstremotevideodecoded" 52 | #define REC_EVENT_FIRST_AUDIO_FRAME "firstremoteaudioframe" 53 | #define RTC_EVENT_AUDIO_VOLUME_INDICATION "audiovolumeindication" 54 | #define REC_EVENT_REMOTE_VIDEO_STREAM_STATE_CHANGED "remoteVideoStreamStateChanged" 55 | #define REC_EVENT_REMOTE_AUDIO_STREAM_STATE_CHANGED "remoteAudioStreamStateChanged" 56 | #define REC_EVENT_REJOIN_SUCCESS "rejoinChannelSuccess" 57 | #define REC_EVENT_CONN_STATE_CHANGED "connectionStateChanged" 58 | #define REC_EVENT_REMOTE_VIDEO_STATS "remoteVideoStats" 59 | #define REC_EVENT_REMOTE_AUDIO_STATS "remoteAudioStats" 60 | #define REC_EVENT_RECORDING_STATS "recordingStats" 61 | #define REC_EVENT_LOCAL_USER_REGISTER "localUserRegistered" 62 | #define REC_EVENT_USER_INFO_UPDATED "userInfoUpdated" 63 | public: 64 | struct NodeEventCallback 65 | { 66 | Persistent callback; 67 | Persistent js_this; 68 | }; 69 | public: 70 | AgoraSdk(); 71 | virtual ~AgoraSdk(); 72 | 73 | virtual bool createChannel(const string &appid, const string &channelKey, const string &name, agora::linuxsdk::uid_t uid, 74 | agora::recording::RecordingConfig &config); 75 | virtual int setVideoMixLayout(); 76 | virtual bool leaveChannel(); 77 | virtual bool release(); 78 | virtual bool stopped() const; 79 | virtual void updateMixModeSetting(int width, int height, bool isVideoMix) { 80 | m_mixRes.m_width = width; 81 | m_mixRes.m_height = height; 82 | m_mixRes.m_videoMix = isVideoMix; 83 | } 84 | virtual const agora::recording::RecordingEngineProperties* getRecorderProperties(); 85 | virtual void updateStorageDir(const char* dir) { m_storage_dir = dir? dir:"./"; } 86 | 87 | virtual int startService(); 88 | virtual int stopService(); 89 | 90 | virtual int setVideoMixingLayout(const agora::linuxsdk::VideoMixingLayout &layout); 91 | virtual agora::recording::RecordingConfig* getConfigInfo() { return &m_config;} 92 | void addEventHandler(const std::string& eventName, Persistent& obj, Persistent& callback); 93 | void updateMixLayout(agora::linuxsdk::VideoMixingLayout &layout); 94 | void emitError(int err, int stat_code); 95 | agora::linuxsdk::VideoMixingLayout getMixLayout(); 96 | protected: 97 | virtual void onError(int error, agora::linuxsdk::STAT_CODE_TYPE stat_code) { 98 | onErrorImpl(error, stat_code); 99 | } 100 | virtual void onWarning(int warn) { 101 | onWarningImpl(warn); 102 | } 103 | 104 | virtual void onJoinChannelSuccess(const char * channelId, agora::linuxsdk::uid_t uid) { 105 | onJoinChannelSuccessImpl(channelId, uid); 106 | } 107 | virtual void onLeaveChannel(agora::linuxsdk::LEAVE_PATH_CODE code) { 108 | onLeaveChannelImpl(code); 109 | } 110 | 111 | virtual void onUserJoined(agora::linuxsdk::uid_t uid, agora::linuxsdk::UserJoinInfos &infos) { 112 | onUserJoinedImpl(uid, infos); 113 | } 114 | virtual void onUserOffline(agora::linuxsdk::uid_t uid, agora::linuxsdk::USER_OFFLINE_REASON_TYPE reason) { 115 | onUserOfflineImpl(uid, reason); 116 | } 117 | 118 | virtual void onActiveSpeaker(uid_t uid); 119 | 120 | virtual void audioFrameReceived(unsigned int uid, const agora::linuxsdk::AudioFrame *frame) const { 121 | audioFrameReceivedImpl(uid, frame); 122 | } 123 | virtual void videoFrameReceived(unsigned int uid, const agora::linuxsdk::VideoFrame *frame) const { 124 | videoFrameReceivedImpl(uid, frame); 125 | } 126 | 127 | virtual void onAudioVolumeIndication(const agora::linuxsdk::AudioVolumeInfo* speakers, unsigned int speakerNum); 128 | virtual void onFirstRemoteVideoDecoded(uid_t uid, int width, int height, int elapsed); 129 | virtual void onFirstRemoteAudioFrame(uid_t uid, int elapsed); 130 | virtual void onReceivingStreamStatusChanged(bool receivingAudio, bool receivingVideo); 131 | virtual void onConnectionLost(); 132 | virtual void onConnectionInterrupted(); 133 | 134 | /** 135 | * 3.0.0 136 | */ 137 | virtual void onRemoteVideoStreamStateChanged(uid_t uid, linuxsdk::RemoteStreamState state, linuxsdk::RemoteStreamStateChangedReason reason); 138 | virtual void onRemoteAudioStreamStateChanged(uid_t, agora::linuxsdk::RemoteStreamState, agora::linuxsdk::RemoteStreamStateChangedReason); 139 | virtual void onRejoinChannelSuccess(const char* channelId, uid_t uid); 140 | virtual void onConnectionStateChanged(agora::linuxsdk::ConnectionStateType state, agora::linuxsdk::ConnectionChangedReasonType reason); 141 | virtual void onRemoteVideoStats(agora::linuxsdk::uid_t uid, const agora::linuxsdk::RemoteVideoStats& stats); 142 | virtual void onRemoteAudioStats(agora::linuxsdk::uid_t uid, const agora::linuxsdk::RemoteAudioStats& stats); 143 | virtual void onRecordingStats(const agora::linuxsdk::RecordingStats& stats); 144 | virtual void onLocalUserRegistered(uid_t uid, const char* userAccount); 145 | virtual void onUserInfoUpdated(uid_t uid, const agora::linuxsdk::UserInfo& info); 146 | protected: 147 | void onErrorImpl(int error, agora::linuxsdk::STAT_CODE_TYPE stat_code); 148 | void onWarningImpl(int warn); 149 | 150 | void onJoinChannelSuccessImpl(const char * channelId, agora::linuxsdk::uid_t uid); 151 | void onLeaveChannelImpl(agora::linuxsdk::LEAVE_PATH_CODE code); 152 | 153 | void onUserJoinedImpl(agora::linuxsdk::uid_t uid, agora::linuxsdk::UserJoinInfos &infos); 154 | void onUserOfflineImpl(agora::linuxsdk::uid_t uid, agora::linuxsdk::USER_OFFLINE_REASON_TYPE reason); 155 | 156 | void audioFrameReceivedImpl(unsigned int uid, const agora::linuxsdk::AudioFrame *frame) const; 157 | void videoFrameReceivedImpl(unsigned int uid, const agora::linuxsdk::VideoFrame *frame) const; 158 | void onAudioVolumeIndication_node(const agora::linuxsdk::AudioVolumeInfo* sperkers, unsigned int speakerNumber); 159 | protected: 160 | atomic_bool_t m_stopped; 161 | std::vector m_peers; 162 | std::string m_logdir; 163 | std::string m_storage_dir; 164 | MixModeSettings m_mixRes; 165 | agora::recording::RecordingConfig m_config; 166 | agora::recording::IRecordingEngine *m_engine; 167 | std::unordered_map m_callbacks; 168 | agora::linuxsdk::VideoMixingLayout m_layout; 169 | }; 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/agora_node_ext.cpp: -------------------------------------------------------------------------------- 1 | #include "agora_node_ext.h" 2 | #include "agora_node_recording.h" 3 | using v8::Object; 4 | using agora::recording::NodeRecordingSdk; 5 | 6 | /** 7 | * Initialize NODEJS ADDON 8 | */ 9 | void InitExt(Local module) 10 | { 11 | LOG_ENTER; 12 | NodeRecordingSdk::Init(module); 13 | LOG_LEAVE; 14 | } 15 | 16 | /** 17 | * NODEJS registration 18 | */ 19 | NAPI_MODULE(NODE_GYP_MODULE_NAME, InitExt) 20 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/agora_node_ext.h: -------------------------------------------------------------------------------- 1 | #ifndef AGORA_NODE_EXT_H 2 | #define AGORA_NODE_EXT_H 3 | //#include "IAgoraRtcEngine.h" 4 | #include "node_log.h" 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/agora_node_recording.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "agora_node_recording.h" 4 | #include "IAgoraLinuxSdkCommon.h" 5 | #include "node_log.h" 6 | #include 7 | #include 8 | #include 9 | using namespace std; 10 | using std::string; 11 | //using namespace v8; 12 | 13 | #define NODE_UID_TYPE 14 | 15 | namespace agora { 16 | namespace recording { 17 | DEFINE_CLASS(NodeRecordingSdk); 18 | 19 | void NodeRecordingSdk::Init(Local &module) 20 | { 21 | Isolate *isolate = module->GetIsolate(); 22 | signal(SIGPIPE, SIG_IGN); 23 | BEGIN_PROPERTY_DEFINE(NodeRecordingSdk, createInstance, 2) //NodeRecordingSdk count of member var 24 | PROPERTY_METHOD_DEFINE(joinChannel) 25 | PROPERTY_METHOD_DEFINE(leaveChannel) 26 | PROPERTY_METHOD_DEFINE(setMixLayout) 27 | PROPERTY_METHOD_DEFINE(onEvent) 28 | PROPERTY_METHOD_DEFINE(release) 29 | EN_PROPERTY_DEFINE() 30 | module->Set(String::NewFromUtf8(isolate, "NodeRecordingSdk"), tpl->GetFunction()); 31 | } 32 | 33 | //The function is used as class constructor in JS layer 34 | 35 | void NodeRecordingSdk::createInstance(const FunctionCallbackInfo &args) 36 | { 37 | LOG_ENTER; 38 | Isolate *isolate = args.GetIsolate(); 39 | //Called from new 40 | if (args.IsConstructCall()) 41 | { 42 | NodeRecordingSdk *recording = new NodeRecordingSdk(isolate); 43 | recording->Wrap(args.This()); 44 | args.GetReturnValue().Set(args.This()); 45 | } 46 | else 47 | { 48 | Local cons = Local::New(isolate, constructor); 49 | Local context = isolate->GetCurrentContext(); 50 | Local instance = cons->NewInstance(context).ToLocalChecked(); 51 | args.GetReturnValue().Set(instance); 52 | } 53 | LOG_LEAVE; 54 | } 55 | NodeRecordingSdk::NodeRecordingSdk(Isolate *isolate) 56 | : m_isolate(isolate) 57 | { 58 | LOG_ENTER; 59 | m_agorasdk = new AgoraSdk(); 60 | m_resolutionMap = { 61 | {"3840x2160", "12000"}, 62 | {"2560x1440", "6400"}, 63 | {"1920x1080", "4000"}, 64 | {"1280x720", "2400"}, 65 | {"960x720", "1920"}, 66 | {"848x480", "1200"}, 67 | {"640x480", "1000"}, 68 | {"480x480", "800"}, 69 | {"640x360", "800"}, 70 | {"360x360", "520"}, 71 | {"424x240", "440"}, 72 | {"320x240", "360"}, 73 | {"240x240", "280"}, 74 | {"320x180", "280"}, 75 | {"240x180", "240"}, 76 | {"180x180", "200"}, 77 | {"160x120", "120"}, 78 | {"120x120", "100"} 79 | }; 80 | LOG_LEAVE; 81 | } 82 | 83 | NAPI_API_DEFINE(NodeRecordingSdk, joinChannel) 84 | { 85 | LOG_ENTER; 86 | cout << "joinChannel..." << endl; 87 | 88 | do{ 89 | agora::recording::RecordingConfig config; 90 | 91 | NodeString key, name, chan_info, applitDir, appid, cfgPath; 92 | uid_t uid; 93 | NodeRecordingSdk *pRecording = NULL; 94 | napi_get_native_this(args, pRecording); 95 | CHECK_NATIVE_THIS(pRecording); 96 | napi_status status = napi_get_value_nodestring_(args[0], key); 97 | CHECK_NAPI_STATUS(status); 98 | status = napi_get_value_nodestring_(args[1], name); 99 | CHECK_NAPI_STATUS(status); 100 | 101 | status = napi_get_value_nodestring_(args[2], applitDir); 102 | cout << "appliteDir " << applitDir << endl; 103 | CHECK_NAPI_STATUS(status); 104 | 105 | status = napi_get_value_nodestring_(args[3], appid); 106 | CHECK_NAPI_STATUS(status); 107 | status = NodeUid::getUidFromNodeValue(args[4], uid); 108 | CHECK_NAPI_STATUS(status); 109 | status = napi_get_value_nodestring_(args[5], cfgPath); 110 | CHECK_NAPI_STATUS(status); 111 | string str_appid = (string)appid; 112 | string str_name = (string)name; 113 | string str_appliteDir = (string)applitDir; 114 | string str_cfgPath = (string)cfgPath; 115 | string str_key; 116 | 117 | if(key == nullptr) { 118 | str_key = ""; 119 | } else { 120 | str_key = (string)key; 121 | } 122 | 123 | config.appliteDir = const_cast(str_appliteDir.c_str()); 124 | config.cfgFilePath = const_cast(str_cfgPath.c_str()); 125 | config.isMixingEnabled = true; 126 | config.mixedVideoAudio = agora::linuxsdk::MIXED_AV_CODEC_V2; 127 | config.idleLimitSec = 10; 128 | // config.decodeVideo = agora::linuxsdk::VIDEO_FORMAT_MIX_JPG_FILE_TYPE; 129 | config.channelProfile = agora::linuxsdk::CHANNEL_PROFILE_LIVE_BROADCASTING; 130 | // config.captureInterval = 1; 131 | config.triggerMode = agora::linuxsdk::AUTOMATICALLY_MODE; 132 | config.mixResolution = "640,480,15,500"; 133 | 134 | agora::linuxsdk::VideoMixingLayout layout = pRecording->m_agorasdk->getMixLayout(); 135 | std::stringstream out; 136 | out << layout.canvasWidth << "x" << layout.canvasHeight; 137 | string resolutionKey = out.str(); 138 | string resolutionValue; 139 | map::iterator it = pRecording->m_resolutionMap.find(resolutionKey); 140 | if (it == pRecording->m_resolutionMap.end()) { 141 | // no values found, throw error 142 | cout << "unsupported resolution" << endl; 143 | int err = 2000, stat_code = 4; 144 | pRecording->m_agorasdk->emitError(err, stat_code); 145 | break; 146 | } else { 147 | resolutionValue = pRecording->m_resolutionMap[it->first]; 148 | } 149 | out.str(""); 150 | out << layout.canvasWidth << "," << layout.canvasHeight << ",15," << resolutionValue; 151 | string mixResolution = out.str(); 152 | config.mixResolution = &mixResolution[0u]; 153 | config.audioIndicationInterval = 0; 154 | //todo 155 | // pRecording->m_agorasdk->updateMixModeSetting(0, 0, true); 156 | int result = pRecording->m_agorasdk->createChannel(str_appid, str_key, str_name, uid, config); 157 | cout << "pRecording->m_agorasdk->createChannel return result:" << result << endl; 158 | napi_set_int_result(args, result); 159 | } while(false); 160 | LOG_LEAVE; 161 | } 162 | 163 | NAPI_API_DEFINE(NodeRecordingSdk, leaveChannel) 164 | { 165 | LOG_ENTER; 166 | cout << "leaveChannel..." << endl; 167 | do 168 | { 169 | NodeRecordingSdk *pRecording = NULL; 170 | napi_get_native_this(args, pRecording); 171 | CHECK_NATIVE_THIS(pRecording); 172 | /*std::shared_ptr chan; 173 | for (auto& channel : pEngine->m_channels) { 174 | if (channel->getUid() == 0) { 175 | chan = channel; 176 | break; 177 | } 178 | } 179 | pEngine->m_channels.clear(); 180 | pEngine->m_channels.push_back(chan);*/ 181 | int result = pRecording->m_agorasdk->leaveChannel(); 182 | napi_set_int_result(args, result); 183 | } while (false); 184 | LOG_LEAVE; 185 | } 186 | 187 | NAPI_API_DEFINE(NodeRecordingSdk, setMixLayout) 188 | { 189 | LOG_ENTER; 190 | cout << "setting mix layout..." << endl; 191 | do 192 | { 193 | NodeRecordingSdk *pRecording = NULL; 194 | napi_get_native_this(args, pRecording); 195 | CHECK_NATIVE_THIS(pRecording); 196 | 197 | int canvasWidth, canvasHeight; 198 | NodeString backgroundColor; 199 | Local regions; 200 | Local layoutData = args[0]->ToObject(args.GetIsolate()); 201 | napi_status status = napi_get_value_int32_object_(args.GetIsolate(), layoutData, "canvasWidth", canvasWidth); 202 | CHECK_NAPI_STATUS(status); 203 | 204 | status = napi_get_value_int32_object_(args.GetIsolate(), layoutData, "canvasHeight", canvasHeight); 205 | CHECK_NAPI_STATUS(status); 206 | 207 | status = napi_get_value_string_object_(args.GetIsolate(), layoutData, "backgroundColor", backgroundColor); 208 | CHECK_NAPI_STATUS(status); 209 | 210 | status = napi_get_value_array_object_(args.GetIsolate(), layoutData, "regions", regions); 211 | CHECK_NAPI_STATUS(status); 212 | 213 | agora::linuxsdk::VideoMixingLayout::Region * regionList = new agora::linuxsdk::VideoMixingLayout::Region[regions->Length()]; 214 | for(size_t i = 0; i < regions->Length(); i++) { 215 | int zOrder; 216 | double alpha, width, height, x, y; 217 | uid_t uid; 218 | Local regionValue = regions->Get(i); 219 | if(!regionValue->IsObject()) { 220 | cout << "invalid region found: " << i << endl; 221 | break; 222 | } 223 | Local region = Local::Cast(regionValue); 224 | 225 | napi_status regionStatus = napi_get_value_double_object_(args.GetIsolate(), region, "x", x); 226 | CHECK_NAPI_STATUS(regionStatus); 227 | 228 | regionStatus = napi_get_value_double_object_(args.GetIsolate(), region, "y", y); 229 | CHECK_NAPI_STATUS(regionStatus); 230 | 231 | regionStatus = napi_get_value_int32_object_(args.GetIsolate(), region, "zOrder", zOrder); 232 | CHECK_NAPI_STATUS(regionStatus); 233 | 234 | regionStatus = napi_get_value_double_object_(args.GetIsolate(), region, "alpha", alpha); 235 | CHECK_NAPI_STATUS(regionStatus); 236 | 237 | regionStatus = napi_get_value_double_object_(args.GetIsolate(), region, "width", width); 238 | CHECK_NAPI_STATUS(regionStatus); 239 | 240 | regionStatus = napi_get_value_double_object_(args.GetIsolate(), region, "height", height); 241 | CHECK_NAPI_STATUS(regionStatus); 242 | 243 | regionStatus = napi_get_value_uint32_object_(args.GetIsolate(), region, "uid", uid); 244 | CHECK_NAPI_STATUS(regionStatus); 245 | 246 | regionList[i].uid = uid; 247 | regionList[i].x = x / canvasWidth; 248 | regionList[i].y = y / canvasHeight; 249 | regionList[i].width = width / canvasWidth; 250 | regionList[i].height = height / canvasHeight; 251 | regionList[i].alpha = alpha; 252 | // regionList[i].zOrder = zOrder; 253 | regionList[i].renderMode = 0; 254 | } 255 | 256 | agora::linuxsdk::VideoMixingLayout layout; 257 | string backgroundColor_str = (string)backgroundColor; 258 | size_t str_len = backgroundColor_str.length(); 259 | char* backgroundColor_copy = new char[str_len]; 260 | strcpy(backgroundColor_copy, backgroundColor_str.c_str()); 261 | layout.canvasWidth = canvasWidth; 262 | layout.canvasHeight = canvasHeight; 263 | layout.backgroundColor = backgroundColor_copy; 264 | layout.regionCount = regions->Length(); 265 | layout.regions = regionList; 266 | 267 | pRecording->m_agorasdk->updateMixLayout(layout); 268 | 269 | } while (false); 270 | LOG_LEAVE; 271 | } 272 | 273 | NAPI_API_DEFINE(NodeRecordingSdk, onEvent) 274 | 275 | { 276 | 277 | //LOG_ENTER; 278 | 279 | do 280 | { 281 | NodeRecordingSdk *pEngine = nullptr; 282 | 283 | napi_status status = napi_ok; 284 | 285 | napi_get_native_this(args, pEngine); 286 | 287 | CHECK_NATIVE_THIS(pEngine); 288 | 289 | NodeString eventName; 290 | 291 | status = napi_get_value_nodestring_(args[0], eventName); 292 | 293 | CHECK_NAPI_STATUS(status); 294 | 295 | if (!args[1]->IsFunction()) 296 | { 297 | 298 | LOG_ERROR("Function expected"); 299 | 300 | break; 301 | } 302 | 303 | Local callback = args[1].As(); 304 | 305 | if (callback.IsEmpty()) 306 | { 307 | 308 | LOG_ERROR("Function expected."); 309 | 310 | break; 311 | } 312 | 313 | Persistent persist; 314 | 315 | persist.Reset(args.GetIsolate(), callback); 316 | 317 | Local obj = args.This(); 318 | 319 | Persistent persistObj; 320 | 321 | persistObj.Reset(args.GetIsolate(), obj); 322 | 323 | pEngine->m_agorasdk->addEventHandler((char *)eventName, persistObj, persist); 324 | 325 | } while (false); 326 | 327 | //LOG_LEAVE; 328 | } 329 | 330 | NAPI_API_DEFINE(NodeRecordingSdk, release) 331 | { 332 | do 333 | { 334 | NodeRecordingSdk *pEngine = nullptr; 335 | napi_status status = napi_ok; 336 | napi_get_native_this(args, pEngine); 337 | CHECK_NATIVE_THIS(pEngine); 338 | 339 | pEngine->m_agorasdk->release(); 340 | } while (false); 341 | } 342 | 343 | NodeRecordingSdk::~NodeRecordingSdk() { 344 | if(m_agorasdk) { 345 | m_agorasdk->release(); 346 | m_agorasdk = NULL; 347 | } 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/agora_node_recording.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "node_log.h" 5 | #include "node_napi_api.h" 6 | #include "node_uid.h" 7 | #include "./AgoraSdk.h" 8 | 9 | using agora::base::opt_parser; 10 | using v8::Isolate; 11 | using std::map; 12 | using std::string; 13 | 14 | // #define unsigned int uid_t 15 | 16 | 17 | //Used to declare native interface to nodejs 18 | #define NAPI_API(m) \ 19 | static void (m)(const FunctionCallbackInfo& args) 20 | 21 | //Used to define native interface which is exposed to nodejs 22 | #define NAPI_API_DEFINE(cls, method) \ 23 | void cls::method(const FunctionCallbackInfo& args) 24 | 25 | //class NodeEventHandler; 26 | 27 | //class NodeRecordingSdk is the wrapper for AgoraSdk, and is exposed to nodejs as the native interface. 28 | namespace agora { 29 | namespace recording { 30 | class NodeRecordingSdk : public node::ObjectWrap { 31 | public: 32 | //Constructor 33 | static void createInstance(const FunctionCallbackInfo& args); 34 | // Helper function, used to declare all supported native interface that are exposed to nodejs. 35 | static void Init(Local& module); 36 | //Wrapper for RtcEngine functions 37 | NAPI_API(joinChannel); 38 | NAPI_API(leaveChannel); 39 | NAPI_API(setMixLayout); 40 | NAPI_API(onEvent); 41 | NAPI_API(release); 42 | //NAPI_API(leaveChannel); 43 | //private: 44 | NodeRecordingSdk(Isolate *isolate); 45 | ~NodeRecordingSdk(); 46 | private: 47 | DECLARE_CLASS; 48 | AgoraSdk* m_agorasdk; 49 | Isolate* m_isolate; 50 | map< string, string > m_resolutionMap; 51 | }; 52 | /** 53 | * Helper MACRO to check whether the last API call return success. 54 | */ 55 | #define CHECK_NAPI_STATUS(status) \ 56 | if(status != napi_ok) { \ 57 | LOG_ERROR("Error :%s, :%d\n", __FUNCTION__, __LINE__); \ 58 | } 59 | 60 | 61 | //Use to extract native this pointer from JS object 62 | #define napi_get_native_this(args, native) \ 63 | native = ObjectWrap::Unwrap(args.Holder()); 64 | 65 | //Helper MACRO to check whether the extracted native this is valid. 66 | #define CHECK_NATIVE_THIS(recording) \ 67 | if(!recording || !recording->m_agorasdk) { \ 68 | LOG_ERROR("m_agorasdk is null.\n");\ 69 | } 70 | 71 | /* 72 | * to return int value for JS call. 73 | */ 74 | #define napi_set_int_result(args, result) (args).GetReturnValue().Set(Integer::New(args.GetIsolate(), (result))) 75 | 76 | /** 77 | * to return bool value for JS call 78 | */ 79 | #define napi_set_bool_result(args, result) (args).GetReturnValue().Set(v8::Boolean::New(args.GetIsolate(), (result))) 80 | 81 | /* 82 | * to return string value for JS call 83 | */ 84 | #define napi_set_string_result(args, data) \ 85 | Local tmp = String::NewFromUtf8(args.GetIsolate(), data, NewStringType::kInternalized).ToLocalChecked();\ 86 | args.GetReturnValue().Set(tmp); 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_async_queue.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietry and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | 11 | #include "node_async_queue.h" 12 | #include 13 | #include 14 | 15 | namespace agora { 16 | namespace recording { 17 | node_async_call node_async_call::s_instance_; 18 | 19 | node_async_call::node_async_call() 20 | { 21 | node_queue_.reset(new async_queue(uv_default_loop(), std::bind(&node_async_call::run_task, this, std::placeholders::_1))); 22 | } 23 | 24 | node_async_call::~node_async_call() 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_async_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietary and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | #ifndef AGORA_NODE_ASYNC_QUEUE_H 11 | #define AGORA_NODE_ASYNC_QUEUE_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | // #include 23 | #include "uv.h" 24 | #include "node_log.h" 25 | namespace agora { 26 | namespace recording { 27 | using task_type = std::function; 28 | 29 | template 30 | class async_queue 31 | { 32 | async_queue(const async_queue&) = delete; 33 | async_queue& operator=(const async_queue&) = delete; 34 | public: 35 | using callback_type = std::function; 36 | async_queue(uv_loop_t *loop, callback_type&& cb) 37 | : h_((uv_async_t*)malloc(sizeof(uv_async_t))) 38 | , closed_(false) 39 | , cb_(std::move(cb)) 40 | , capacity_(0) 41 | { 42 | ::uv_async_init(loop, h_, async_callback); 43 | h_->data = this; 44 | } 45 | 46 | ~async_queue() { 47 | uv_close((uv_handle_t*)h_, [](uv_handle_t *handle) { 48 | free(handle); 49 | }); 50 | } 51 | int async_call(Elem&& e, uint64_t ts = 0) { 52 | if (closed_) { 53 | return -1; 54 | } 55 | 56 | { 57 | std::lock_guard guard(lock_); 58 | if (capacity_&& q_.size() > capacity_) 59 | { 60 | q_.pop(); 61 | } 62 | q_.push(std::move(e)); 63 | } 64 | 65 | return !uv_async_send(h_) ? 0 : -1; 66 | } 67 | size_t size() const { 68 | std::lock_guard guard(lock_); 69 | return q_.size(); 70 | } 71 | bool empty() const { 72 | return size() == 0; 73 | } 74 | void close() { 75 | if (!empty()) { 76 | // LOG_WARNING(" You should close this queue after taking all the elements!"); 77 | log(LOG_LEVEL_WARNING, " You should close this queue after taking all the elements!"); 78 | } 79 | closed_ = true; 80 | } 81 | bool closed() const { 82 | return closed_; 83 | } 84 | void set_priority(int prio) {} 85 | void set_capacity(size_t capacity) { 86 | capacity_ = capacity; 87 | } 88 | void clear() { 89 | std::lock_guard guard(lock_); 90 | std::queue empty; 91 | std::swap(q_, empty); 92 | } 93 | uint64_t last_pop_ts() const { 94 | return 0; 95 | } 96 | private: 97 | static void async_callback(uv_async_t* handle) { 98 | reinterpret_cast(handle->data)->on_event(); 99 | } 100 | void on_event() { 101 | std::unique_lock lock(lock_); 102 | while (!q_.empty()) { 103 | Elem e(std::move(q_.front())); 104 | q_.pop(); 105 | lock.unlock(); 106 | cb_(e); 107 | lock.lock(); 108 | } 109 | } 110 | private: 111 | uv_async_t * h_; 112 | std::atomic closed_; 113 | mutable Lck lock_; 114 | std::queue q_; 115 | callback_type cb_; 116 | size_t capacity_; 117 | }; 118 | 119 | class node_async_call 120 | { 121 | public: 122 | static void async_call(task_type&& cb) { 123 | node_async_call::instance().node_queue_->async_call(std::move(cb)); 124 | } 125 | 126 | private: 127 | using node_queue_type = async_queue; 128 | node_async_call(); 129 | ~node_async_call(); 130 | static node_async_call& instance() { return s_instance_; } 131 | void run_task(task_type& task) { 132 | task(); 133 | } 134 | std::unique_ptr node_queue_; 135 | static node_async_call s_instance_; 136 | }; 137 | } 138 | } 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietry and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | 11 | #include 12 | #include 13 | #include "node_log.h" 14 | 15 | void log(enum log_level level, const char *format, ...) 16 | { 17 | // TBD 18 | /*va_list la; 19 | va_start(la, format); 20 | vprintf(format, la); 21 | va_end(la);*/ 22 | } 23 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietary and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | #ifndef AGORA_NODE_LOG_H 11 | #define AGORA_NODE_LOG_H 12 | 13 | /** 14 | * Log level definition 15 | */ 16 | enum log_level 17 | { 18 | LOG_LEVEL_ERROR, 19 | LOG_LEVEL_WARNING, 20 | LOG_LEVEL_INFO, 21 | LOG_LEVEL_VERBOSE 22 | }; 23 | 24 | /** 25 | * To write log 26 | * @param level - log level 27 | * @format - message format 28 | */ 29 | void log(enum log_level level, const char *format, ...); 30 | 31 | /** write log with error log level */ 32 | #define LOG_ERROR(format, ...) log(LOG_LEVEL_ERROR, format, ##__VA_ARGS__) 33 | 34 | /** 35 | * write log with warning log level 36 | */ 37 | #define LOG_WARNING(format, ...) log(LOG_LEVEL_WARNING, format, ##__VA_ARGS__) 38 | 39 | /** 40 | * write log with info log level 41 | */ 42 | #define LOG_INFO(format, ...) log(LOG_LEVEL_INFO, format, ##__VA_ARGS__) 43 | 44 | /** 45 | * write log with verbose log level 46 | */ 47 | #define LOG_VERBOSE(format, ...) log(LOG_LEVEL_VERBOSE, format, ##__VA_ARGS__) 48 | 49 | /** 50 | * write function enter log. 51 | */ 52 | #define LOG_ENTER LOG_VERBOSE("==> %s\n", __FUNCTION__) 53 | 54 | /** 55 | * write function leave log. 56 | */ 57 | #define LOG_LEAVE LOG_VERBOSE("<== %s\n", __FUNCTION__) 58 | 59 | #endif -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_napi_api.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietry and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | 11 | #include "node_napi_api.h" 12 | #include "node_uid.h" 13 | 14 | int napi_get_value_string_utf8_(const Local &str, char *buffer, uint32_t len) 15 | { 16 | if (!str->IsString()) 17 | return 0; 18 | if (!buffer) 19 | { 20 | return str.As()->Utf8Length(); 21 | } 22 | else 23 | { 24 | int copied = str.As()->WriteUtf8(buffer, len - 1, nullptr, String::REPLACE_INVALID_UTF8 | String::NO_NULL_TERMINATION); 25 | buffer[copied] = '\0'; 26 | return copied; 27 | } 28 | } 29 | 30 | napi_status napi_get_value_uint32_(const Local &value, uint32_t &result) 31 | { 32 | if (!value->IsUint32()) 33 | return napi_invalid_arg; 34 | result = value->Uint32Value(); 35 | return napi_ok; 36 | } 37 | 38 | napi_status napi_get_value_bool_(const Local &value, bool &result) 39 | { 40 | if (!value->IsBoolean()) 41 | return napi_invalid_arg; 42 | result = value->BooleanValue(); 43 | return napi_ok; 44 | } 45 | 46 | napi_status napi_get_value_int32_(const Local &value, int32_t &result) 47 | { 48 | if (!value->IsInt32()) 49 | return napi_invalid_arg; 50 | result = value->Int32Value(); 51 | return napi_ok; 52 | } 53 | 54 | napi_status napi_get_value_double_(const Local &value, double &result) 55 | { 56 | if (!value->IsNumber()) 57 | return napi_invalid_arg; 58 | 59 | result = value->NumberValue(); 60 | return napi_ok; 61 | } 62 | 63 | napi_status napi_get_value_int64_(const Local &value, int64_t &result) 64 | { 65 | int32_t tmp; 66 | napi_status status = napi_get_value_int32_(value, tmp); 67 | result = tmp; 68 | return status; 69 | } 70 | 71 | napi_status napi_get_value_nodestring_(const Local &str, NodeString &nodechar) 72 | { 73 | napi_status status = napi_ok; 74 | do 75 | { 76 | int len = napi_get_value_string_utf8_(str, nullptr, 0); 77 | if (len == 0) 78 | { 79 | break; 80 | } 81 | char *outstr = NodeString::alloc_buf(len + 1); 82 | if (!outstr) 83 | { 84 | status = napi_generic_failure; 85 | break; 86 | } 87 | len = napi_get_value_string_utf8_(str, outstr, len + 1); 88 | 89 | if (status != napi_ok) 90 | { 91 | break; 92 | } 93 | nodechar.setBuf(outstr); 94 | } while (false); 95 | return status; 96 | } 97 | 98 | Local napi_create_uint32_(Isolate *isolate, const uint32_t &value) 99 | { 100 | return v8::Number::New(isolate, value); 101 | } 102 | 103 | Local napi_create_bool_(Isolate *isolate, const bool &value) 104 | { 105 | return v8::Boolean::New(isolate, value); 106 | } 107 | 108 | Local napi_create_string_(Isolate *isolate, const char *value) 109 | { 110 | return String::NewFromUtf8(isolate, value ? value : ""); 111 | } 112 | 113 | Local napi_create_double_(Isolate *isolate, const double &value) 114 | { 115 | return v8::Number::New(isolate, value); 116 | } 117 | 118 | Local napi_create_uint64_(Isolate *isolate, const uint64_t &value) 119 | { 120 | return v8::Number::New(isolate, value); 121 | } 122 | 123 | Local napi_create_int32_(Isolate *isolate, const int32_t &value) 124 | { 125 | return v8::Int32::New(isolate, value); 126 | } 127 | 128 | Local napi_create_uint16_(Isolate *isolate, const uint16_t &value) 129 | { 130 | return v8::Uint32::New(isolate, value); 131 | } 132 | 133 | Local napi_create_uid_(Isolate *isolate, const uid_t& uid) 134 | { 135 | return NodeUid::getNodeValue(isolate, uid); 136 | } 137 | 138 | napi_status napi_get_value_int32_object_(Isolate *isolate, Local &object, const char *str_key, int32_t &result) 139 | { 140 | Local key = String::NewFromUtf8(isolate, str_key, NewStringType::kInternalized).ToLocalChecked(); 141 | Local value = object->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); 142 | napi_status status = napi_get_value_int32_(value, result); 143 | return status; 144 | } 145 | 146 | napi_status napi_get_value_uint32_object_(Isolate *isolate, Local &object, const char *str_key, uint32_t &result) 147 | { 148 | Local key = String::NewFromUtf8(isolate, str_key, NewStringType::kInternalized).ToLocalChecked(); 149 | Local value = object->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); 150 | napi_status status = napi_get_value_uint32_(value, result); 151 | return status; 152 | } 153 | 154 | napi_status napi_get_value_double_object_(Isolate *isolate, Local &object, const char *str_key, double &result) 155 | { 156 | Local key = String::NewFromUtf8(isolate, str_key, NewStringType::kInternalized).ToLocalChecked(); 157 | Local value = object->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); 158 | napi_status status = napi_get_value_double_(value, result); 159 | return status; 160 | } 161 | 162 | napi_status napi_get_value_string_object_(Isolate *isolate, Local &object, const char *str_key, NodeString &result) 163 | { 164 | Local key = String::NewFromUtf8(isolate, str_key, NewStringType::kInternalized).ToLocalChecked(); 165 | Local value = object->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); 166 | napi_status status = napi_get_value_nodestring_(value, result); 167 | return status; 168 | } 169 | 170 | napi_status napi_get_value_array_object_(Isolate *isolate, Local &object, const char *str_key, Local &result) 171 | { 172 | Local key = String::NewFromUtf8(isolate, str_key, NewStringType::kInternalized).ToLocalChecked(); 173 | Local value = object->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); 174 | if(!value->IsArray()){ 175 | return napi_invalid_arg; 176 | } 177 | result = Local::Cast(value); 178 | return napi_ok; 179 | } 180 | 181 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_napi_api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietary and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | #ifndef AGORA_NODE_NAPI_API_H 11 | #define AGORA_NODE_NAPI_API_H 12 | 13 | /** 14 | * The file defines facilities used to implement node ADDON. 15 | */ 16 | 17 | #include 18 | //#include 19 | using v8::Persistent; 20 | using v8::Function; 21 | using v8::Local; 22 | using v8::FunctionTemplate; 23 | using v8::FunctionCallback; 24 | using v8::FunctionCallbackInfo; 25 | using v8::Value; 26 | using v8::Local; 27 | using v8::Context; 28 | using v8::Object; 29 | using v8::String; 30 | using v8::NewStringType; 31 | using v8::Integer; 32 | using v8::Isolate; 33 | using v8::HandleScope; 34 | using v8::Name; 35 | using v8::Array; 36 | #define NAPI_MODULE(name, fn) NODE_MODULE(name, fn) 37 | 38 | /** 39 | * Node status 40 | */ 41 | typedef enum { 42 | napi_ok, 43 | napi_invalid_arg, 44 | napi_object_expected, 45 | napi_string_expected, 46 | napi_name_expected, 47 | napi_function_expected, 48 | napi_number_expected, 49 | napi_boolean_expected, 50 | napi_array_expected, 51 | napi_generic_failure, 52 | napi_pending_exception, 53 | napi_cancelled, 54 | napi_escape_called_twice, 55 | napi_handle_scope_mismatch 56 | } napi_status; 57 | 58 | /** 59 | * NodeString is used to translate string from V8 value and vice versa in the same way as primitive types. 60 | */ 61 | class NodeString 62 | { 63 | public: 64 | NodeString() 65 | : p_mem(nullptr) 66 | {} 67 | NodeString(char* buf) 68 | : p_mem(buf) 69 | {} 70 | ~NodeString() 71 | { 72 | if (p_mem) { 73 | free_buf(p_mem); 74 | p_mem = nullptr; 75 | } 76 | } 77 | 78 | void setBuf(char* buf) 79 | { 80 | if (p_mem) { 81 | free_buf(p_mem); 82 | p_mem = nullptr; 83 | } 84 | p_mem = buf; 85 | } 86 | 87 | operator char* () { 88 | return p_mem; 89 | } 90 | 91 | static char* alloc_buf(size_t length) 92 | { 93 | return (char*)calloc(length, 1); 94 | } 95 | static void free_buf(char* buf) 96 | { 97 | free(buf); 98 | } 99 | private: 100 | char *p_mem; 101 | }; 102 | 103 | /** To declare class constructor, needed for classes to be exposed to JS layer. */ 104 | #define DECLARE_CLASS \ 105 | static Persistent constructor 106 | /** Define the class */ 107 | #define DEFINE_CLASS(name) \ 108 | Persistent name::constructor 109 | 110 | /** 111 | * used to define class that could be used directly in JS layer. 112 | */ 113 | #define BEGIN_PROPERTY_DEFINE(className, constructor, fieldCount) \ 114 | Local tpl = FunctionTemplate::New(isolate, constructor); \ 115 | tpl->SetClassName(String::NewFromUtf8(isolate, #className)); \ 116 | tpl->InstanceTemplate()->SetInternalFieldCount(fieldCount); 117 | 118 | /** 119 | * Add member functions that could be called in JS layer directly. 120 | */ 121 | #define PROPERTY_METHOD_DEFINE(name) NODE_SET_PROTOTYPE_METHOD(tpl, #name, name); 122 | 123 | #define EN_PROPERTY_DEFINE() \ 124 | constructor.Reset(isolate, tpl->GetFunction()); 125 | 126 | #define NAPI_AUTO_LENGTH SIZE_MAX 127 | 128 | /** 129 | * get the utf8 string from V8 value. 130 | */ 131 | int napi_get_value_string_utf8_(const Local& str, char *buffer, uint32_t len); 132 | 133 | /** 134 | * get uint32 from V8 value. 135 | */ 136 | napi_status napi_get_value_uint32_(const Local& value, uint32_t& result); 137 | 138 | /** 139 | * get bool from V8 value. 140 | */ 141 | napi_status napi_get_value_bool_(const Local& value, bool& result); 142 | 143 | /** 144 | * get int32 from V8 value. 145 | */ 146 | napi_status napi_get_value_int32_(const Local& value, int32_t& result); 147 | 148 | /** 149 | * get double from V8 value. 150 | */ 151 | napi_status napi_get_value_double_(const Local& value, double &result); 152 | 153 | 154 | /** 155 | * get int64 from V8 value. 156 | */ 157 | napi_status napi_get_value_int64_(const Local& value, int64_t& result); 158 | 159 | /** 160 | * get nodestring from V8 value. 161 | */ 162 | napi_status napi_get_value_nodestring_(const Local& str, NodeString& nodechar); 163 | 164 | /** 165 | * Create V8 value from uint32 166 | */ 167 | Local napi_create_uint32_(Isolate *isolate, const uint32_t& value); 168 | 169 | /** 170 | * Create V8 from bool 171 | */ 172 | Local napi_create_bool_(Isolate *isolate, const bool& value); 173 | 174 | /** 175 | * Create V8 from string 176 | */ 177 | Local napi_create_string_(Isolate *isolate, const char* value); 178 | 179 | /** 180 | * Create V8 value from double 181 | */ 182 | Local napi_create_double_(Isolate *isolate, const double &value); 183 | 184 | /** 185 | * Create V8 value from uint64 186 | */ 187 | Local napi_create_uint64_(Isolate *isolate, const uint64_t& value); 188 | 189 | /** 190 | * Create V8 value from int32 191 | */ 192 | Local napi_create_int32_(Isolate *isolate, const int32_t& value); 193 | 194 | /** 195 | * Create V8 value from uint16 196 | */ 197 | Local napi_create_uint16_(Isolate *isolate, const uint16_t& value); 198 | 199 | /** 200 | * Create V8 value from uid 201 | */ 202 | Local napi_create_uid_(Isolate *isolate, const uid_t& uid); 203 | 204 | 205 | napi_status napi_get_value_int32_object_(Isolate *isolate, Local &object, const char *str_key, int32_t &result); 206 | napi_status napi_get_value_uint32_object_(Isolate *isolate, Local &object, const char *str_key, uint32_t &result); 207 | napi_status napi_get_value_double_object_(Isolate *isolate, Local &object, const char *str_key, double &result); 208 | napi_status napi_get_value_string_object_(Isolate *isolate, Local &object, const char *str_key, NodeString &result); 209 | napi_status napi_get_value_array_object_(Isolate *isolate, Local &object, const char *str_key, Local &result); 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_uid.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietry and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/node_uid.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Agora.io 3 | * All rights reserved. 4 | * Proprietry and Confidential -- Agora.io 5 | */ 6 | 7 | /* 8 | * Created by Wang Yongli, 2017 9 | */ 10 | 11 | #ifndef AGORA_NODE_UID_H 12 | #define AGORA_NODE_UID_H 13 | 14 | #include "node.h" 15 | #include "node_napi_api.h" 16 | /** 17 | * The class is used to translation from uid_t to string and vice versa. 18 | * Currently uid_t is uint32 type, and maybe string type in future. 19 | */ 20 | #define NODE_UID_TYPE int32 21 | class NodeUid 22 | { 23 | public: 24 | NodeUid(uid_t id) 25 | : m_id(id) 26 | {} 27 | ~NodeUid() 28 | {} 29 | 30 | operator uid_t() 31 | { 32 | return m_id; 33 | } 34 | 35 | /** 36 | * To generate V8 type from uid_t 37 | * @param isolate : Isolate context 38 | * @param id : uid_t type to be transferred to V8 type. 39 | */ 40 | static Local getNodeValue(Isolate *isolate, uid_t id) 41 | { 42 | return v8::Number::New(isolate, id); 43 | } 44 | 45 | /** 46 | * To get uid_t type value from V8 type 47 | * @param value : the V8 value type contains uid_t type value. 48 | * @param id : the uid_t type reference used to store the value 49 | */ 50 | static napi_status getUidFromNodeValue(const Local& value, uid_t& id) 51 | { 52 | napi_status status = napi_get_value_uint32_(value, id); 53 | return status; 54 | } 55 | 56 | private: 57 | uid_t m_id; 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /On-Premise-Recording-Nodejs/record/src/agora_node_ext/opt_parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #if (defined(_WIN32) || defined(_WIN64)) && !defined(__GNUC__) 10 | enum {ERROR=-1, INFO=0, WARNING, FATAL}; 11 | #define LOG(level, fmt, ...) fprintf(stderr, #level fmt "\n", __VA_ARGS__) 12 | #define strtoll _strtoi64 13 | #define strtoull _strtoui64 14 | #else 15 | #include 16 | #include "base/log.h" 17 | #endif 18 | 19 | #include "base/opt_parser.h" 20 | 21 | using namespace std; 22 | 23 | namespace agora { 24 | namespace base { 25 | #if defined(_WIN32) && !defined(__GNUC__) 26 | 27 | enum {no_argument = 0, required_argument, optional_argument}; 28 | 29 | int optind = 0; 30 | const char *optarg = NULL; 31 | 32 | struct option { 33 | const char *name; 34 | int has_arg; 35 | int *flag; 36 | int val; 37 | }; 38 | 39 | int getopt_long_only(int argc, char *const argv[], const char *short_opts, 40 | const option *long_opts, int *index) { 41 | if (short_opts && *short_opts != '\0') { 42 | LOG(ERROR, "short options have not been implemented yet!"); 43 | return -1; 44 | } 45 | 46 | optind = optind == 0 ? 1 : optind; 47 | optarg = NULL; 48 | 49 | if (optind >= argc) 50 | return -1; 51 | 52 | const char *a = argv[optind]; 53 | if (a[0] != '-' || a[1] != '-') 54 | return -1; 55 | 56 | a += 2; 57 | 58 | int i = 0; 59 | 60 | for (; long_opts[i].name != NULL; ++i) { 61 | if (!strcmp(long_opts[i].name, a)) { 62 | ++optind; 63 | *index = i; 64 | return 0; 65 | } 66 | } 67 | 68 | return '?'; 69 | } 70 | 71 | #endif 72 | 73 | // bool opt_parser::insert_short_arg(internal_opt arg, char short_arg) { 74 | // if (!isalpha(short_arg)) { 75 | // LOG(ERROR, "short parameters should be alphabetic!"); 76 | // return false; 77 | // } 78 | // 79 | // if (short_args_.count(short_arg) > 0) { 80 | // LOG(ERROR, "short parameter[%c] has been occupied!", short_arg); 81 | // return false; 82 | // } 83 | // 84 | // short_args_[short_arg] = arg; 85 | // return true; 86 | // } 87 | opt_parser::opt_parser():sequence_(0) 88 | {} 89 | bool opt_parser::insert_long_opt(internal_opt &opt, const char *long_opt) { 90 | if (!long_opt) { 91 | LOG(ERROR, "A full parameter should be supplied!"); 92 | return false; 93 | } 94 | 95 | if (long_opts_.count(long_opt) > 0) { 96 | LOG(ERROR, "{%s} has been occupied yet!", long_opt); 97 | return false; 98 | } 99 | if(opt.help == NULL || !strlen(opt.help)) 100 | opt.help = "NA"; 101 | 102 | long_opts_[long_opt] = opt; 103 | return true; 104 | } 105 | 106 | // bool opt_parser::add_short_arg(bool *store, char short_arg) { 107 | // *store = false; 108 | // 109 | // internal_opt arg = {kBool, {store}}; 110 | // return insert_short_arg(arg, short_arg); 111 | // } 112 | // 113 | // bool opt_parser::add_short_arg(int *store, char short_arg) { 114 | // internal_opt arg; 115 | // arg.type = kInt; 116 | // arg.int_ptr = store; 117 | // 118 | // return insert_short_arg(arg, short_arg); 119 | // } 120 | // 121 | // bool opt_parser::add_short_arg(string *store, char short_arg) { 122 | // internal_opt arg; 123 | // arg.type = kString; 124 | // arg.string_ptr = store; 125 | // 126 | // return insert_short_arg(arg, short_arg); 127 | // } 128 | 129 | bool opt_parser::add_long_opt(const char *long_opt, bool *store, 130 | const char *help, opt_parser::opt_type optParamType) 131 | { 132 | internal_opt arg; 133 | arg.type = kBool; 134 | arg.bool_ptr = store; 135 | arg.help = help; 136 | arg.optType = optParamType; 137 | arg.seq = ++sequence_; 138 | return insert_long_opt(arg, long_opt); 139 | } 140 | 141 | bool opt_parser::add_long_opt(const char *long_opt, int32_t *store, 142 | const char *help, opt_parser::opt_type optParamType) 143 | { 144 | internal_opt arg; 145 | arg.type = kInt32; 146 | arg.int32_ptr = store; 147 | arg.help = help; 148 | arg.optType = optParamType; 149 | arg.seq = ++sequence_; 150 | return insert_long_opt(arg, long_opt); 151 | } 152 | 153 | bool opt_parser::add_long_opt(const char *long_opt, uint32_t *store, 154 | const char *help, opt_parser::opt_type optParamType) 155 | { 156 | internal_opt arg; 157 | arg.type = kUInt32; 158 | arg.uint32_ptr = store; 159 | arg.help = help; 160 | arg.optType = optParamType; 161 | arg.seq = ++sequence_; 162 | return insert_long_opt(arg, long_opt); 163 | } 164 | 165 | bool opt_parser::add_long_opt(const char *long_opt, int64_t *store, 166 | const char *help, opt_parser::opt_type optParamType) 167 | { 168 | internal_opt arg; 169 | arg.type = kInt64; 170 | arg.int64_ptr = store; 171 | arg.help = help; 172 | arg.optType = optParamType; 173 | arg.seq = ++sequence_; 174 | return insert_long_opt(arg, long_opt); 175 | } 176 | 177 | bool opt_parser::add_long_opt(const char *long_opt, uint64_t *store, 178 | const char *help, opt_parser::opt_type optParamType) 179 | { 180 | internal_opt arg; 181 | arg.type = kUInt64; 182 | arg.uint64_ptr = store; 183 | arg.help = help; 184 | arg.optType = optParamType; 185 | arg.seq = ++sequence_; 186 | return insert_long_opt(arg, long_opt); 187 | } 188 | 189 | bool opt_parser::add_long_opt(const char *long_opt, double *store, 190 | const char *help, opt_parser::opt_type optParamType) 191 | { 192 | internal_opt arg; 193 | arg.type = kDouble; 194 | arg.double_ptr = store; 195 | arg.help = help; 196 | arg.optType = optParamType; 197 | arg.seq = ++sequence_; 198 | return insert_long_opt(arg, long_opt); 199 | } 200 | 201 | bool opt_parser::add_long_opt(const char *long_opt, string *store, 202 | const char *help, opt_parser::opt_type optParamType) 203 | { 204 | internal_opt arg; 205 | arg.type = kString; 206 | arg.string_ptr = store; 207 | arg.help = help; 208 | arg.optType = optParamType; 209 | arg.seq = ++sequence_; 210 | return insert_long_opt(arg, long_opt); 211 | } 212 | 213 | bool opt_parser::add_long_opt(const char *long_opt, ipv4 *store, 214 | const char *help, opt_parser::opt_type optParamType) 215 | { 216 | internal_opt arg; 217 | arg.type = kIPv4; 218 | arg.ipv4_ptr = store; 219 | arg.help = help; 220 | arg.optType = optParamType; 221 | arg.seq = ++sequence_; 222 | return insert_long_opt(arg, long_opt); 223 | } 224 | 225 | bool opt_parser::add_long_opt(const char *long_opt, mac_addr *store, 226 | const char *help, opt_parser::opt_type optParamType) 227 | { 228 | internal_opt arg; 229 | arg.type = kMacAddr; 230 | arg.addr_ptr = store; 231 | arg.help = help; 232 | arg.optType = optParamType; 233 | arg.seq = ++sequence_; 234 | return insert_long_opt(arg, long_opt); 235 | } 236 | 237 | bool opt_parser::parse_ipv4(const char *arg, ipv4 *ip) { 238 | uint8_t (&a)[4] = ip->repr; 239 | if (sscanf(arg, "%hhu.%hhu.%hhu.%hhu", &a[0], &a[1], &a[2], &a[3]) != 4) { 240 | LOG(ERROR, "Illegal IP Format: %s", arg); 241 | return false; 242 | } 243 | 244 | return true; 245 | } 246 | 247 | bool opt_parser::parse_mac_addr(const char *arg, mac_addr *addr) { 248 | uint8_t (&b)[6] = addr->addr_bytes; 249 | if (sscanf(arg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &b[0], &b[1], 250 | &b[2], &b[3], &b[4], &b[5]) != 6) { 251 | LOG(ERROR, "Illegal ethernet physical address: %s", arg); 252 | return false; 253 | } 254 | 255 | return true; 256 | } 257 | 258 | bool opt_parser::fill_arg(const char *opt_name, const internal_opt &opt, 259 | const char *opt_arg) { 260 | 261 | if (opt_arg == NULL) { 262 | LOG(ERROR, "No argument available for %s", opt_name); 263 | return false; 264 | } 265 | 266 | char *end_ptr = NULL; 267 | 268 | switch (opt.type) { 269 | case kBool: { 270 | unsigned long n = strtoul(opt_arg, &end_ptr, 10); 271 | if (*end_ptr != '\0') { 272 | LOG(ERROR, "Invalid integer argument: %s", opt_arg); 273 | return false; 274 | } 275 | *opt.bool_ptr = n? true:false; 276 | } 277 | break; 278 | 279 | case kInt32: { 280 | long n = strtol(opt_arg, &end_ptr, 10); 281 | if (*end_ptr != '\0') { 282 | LOG(ERROR, "Invalid integer argument: %s", opt_arg); 283 | return false; 284 | } 285 | *opt.int32_ptr = int32_t(n); 286 | break; 287 | } 288 | case kUInt32: { 289 | unsigned long n = strtoul(opt_arg, &end_ptr, 10); 290 | if (*end_ptr != '\0') { 291 | LOG(ERROR, "Invalid integer argument: %s", opt_arg); 292 | return false; 293 | } 294 | *opt.uint32_ptr = uint32_t(n); 295 | break; 296 | } 297 | case kInt64: { 298 | long long n = strtoll(opt_arg, &end_ptr, 10); 299 | if (*end_ptr != '\0') { 300 | LOG(ERROR, "Invalid integer argument: %s", opt_arg); 301 | return false; 302 | } 303 | *opt.int64_ptr = int64_t(n); 304 | break; 305 | } 306 | case kUInt64: { 307 | unsigned long long n = strtoull(opt_arg, &end_ptr, 10); 308 | if (*end_ptr != '\0') { 309 | LOG(ERROR, "Invalid integer argument: %s", opt_arg); 310 | return false; 311 | } 312 | *opt.uint64_ptr = uint64_t(n); 313 | break; 314 | } 315 | case kDouble: { 316 | double n = strtod(opt_arg, &end_ptr); 317 | if (*end_ptr != '\0') { 318 | LOG(ERROR, "Invalid double argument: %s", opt_arg); 319 | return false; 320 | } 321 | *opt.double_ptr = n; 322 | break; 323 | } 324 | case kString: { 325 | *opt.string_ptr = opt_arg; 326 | break; 327 | } 328 | case kIPv4: { 329 | if (!parse_ipv4(opt_arg, opt.ipv4_ptr)) 330 | return false; 331 | break; 332 | } 333 | case kMacAddr: { 334 | if (!parse_mac_addr(opt_arg, opt.addr_ptr)) 335 | return false; 336 | break; 337 | } 338 | default: assert(false); break; 339 | } 340 | 341 | return true; 342 | } 343 | 344 | void opt_parser::save_to_exopts()const 345 | { 346 | typedef unordered_map::const_iterator It; 347 | for(It iter=long_opts_.begin(); iter != long_opts_.end(); ++ iter) 348 | { 349 | ex_internal_opt ex_opt; 350 | ex_opt.long_opt = iter->first; 351 | ex_opt.stru_opt = iter->second; 352 | int key = iter->second.seq; 353 | ex_long_opts_[key]=ex_opt; 354 | } 355 | } 356 | bool opt_parser::parse_opts(int argc, char* const argv[]) { 357 | // vector options; 358 | // options.reserve(short_args_.size() + 1); 359 | // 360 | // unordered_map::const_iterator it; 361 | // for (it = short_args_.begin(); it != short_args_.end(); ++it) { 362 | // options.push_back(it->first); 363 | // if (it->second.type != kBool) 364 | // options.push_back(':'); 365 | // } 366 | // 367 | // options.push_back('\0'); 368 | 369 | vector