使用 Node.js SDK 开始云端录制
本文向新手介绍如何使用声网 Node.js SDK 开始第一次云端录制。
Node.js SDK 旨在帮助开发者更轻松集成声网 RESTful API。该 SDK 具有如下特性:
- 简化通信流畅:通过封装 RESTful API 的请求和响应处理,让你与 RESTful API 的通信更加简单。
- 保障可用性:遇到 DNS 解析失败、网络错误、请求超时等网络问题时,会自动切换到最佳域名,确保 REST 服务的可用性。
- API 易用性:提供简洁易懂的 API,让你能轻松实现开启云端录制、停止云端录制等常用功能。
- 其他优势:基于 Node.js 语言编写,具有高效性、并发性、可扩展性。
前提条件
开始前请确保你的本地开发环境满足如下条件:
-
Node.js 18 或以上版本。
-
参考开通服务在声网控制台开通云端录制,并复制保存 App ID、临时 Token、客户 ID 和客户密钥。
注意临时 Token 的有效期是 24 小时。Token 过期会导致加入频道失败。
-
云端录制文件需要上传到第三方云存储。开始录制前,请开通第三方云存储服务,并获取 Bucket、Access Key、Secret Key 以备用。
-
在项目中实现了基础的音视频互动场景。你也可以参考文档使用声网实时互动 Web Demo 在浏览器中模拟一个音视频互动场景。
创建项目并安装 SDK
创建项目
参考如下步骤创建并配置新项目。
-
创建一个名为
test-recording
的空文件夹。 -
进入
test-recording
文件路径,运行如下命令初始化项目:Shellnpm init -y
完成初始化后,项目路径下会自动创建一个
package.json
文件。 -
在
test-recording/
路径下,创建一个src/index.ts
文件,我们会在这个文件中实现一个极简的云端录制任务。
添加 TypeScript 依赖
由于使用 .ts
文件,我们需要在项目文件夹中添加对 TypeScript 的依赖。
-
运行如下命令行安装 TypeScript:
Shellnpm install typescript @types/node
完成安装后项目路径下会自动创建一个
package-lock.json
文件。 -
配置 TypeScript 的运行环境。
-
运行如下命令行创建 TypeScript 配置文件:
Shellnpx tsc --init
完成后项目路径下会自动创建一个
tsconfig.json
文件。将tsconfig.json
中的代码替换为如下行:JSON{
"compileOnSave": true,
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"module": "CommonJS",
"moduleResolution": "node",
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"removeComments": false,
"resolveJsonModule": true,
"skipLibCheck": true,
"strictNullChecks": true,
"target": "ES2015",
"baseUrl": ".",
"outDir": "dist",
"rootDir": "./src",
"sourceMap": true,
"declaration": true
},
"include": ["./src"],
"exclude": ["__tests__"]
} -
运行如下命令行创建一个 TypeScript 建构文件:
Shelltouch tsconfig.build.json
将
tsconfig.build.json
中的代码替换为如下行:JSON{
"extends": "./tsconfig",
"compilerOptions": {
"sourceMap": false
}
}
-
-
在根目录的
package.json
文件的scripts
区域,添加如下行:JSON"build": "tsc --project tsconfig.build.json &&node dist/index.js"
添加 REST Client 依赖库
运行如下命令行添加依赖:
npm install agora-rest-client
完成添加后,项目文件下会添加一个 node_modules
文件包。
开始云端录制
本节介绍进行云端录制的极简流程。为方便说明,本节以录制之前模拟的音视频直播为例,并假设录制主播的音频和视频到 MP4 文件中。
引入依赖
在 src/index.ts
中添加如下行,引入 agora-rest-client
及相关代码模块:
import {
DomainArea,
BasicCredential,
CloudRecordingClient,
AcquireResourceRes,
StartResourceRes,
RecordingRequestChannelTypeEnum,
QueryMixHLSAndMP4RecordingResourceRes,
StopResourceRes,
} from "agora-rest-client";
定义变量
继续添加如下代码,定义和配置关键参数。取值参考前提条件。
// 定义关键参数
const appId = "<your_app_id>";
const cname = "show";
const uid = "123";
const username = "<your_customer_id>";
const password = "<your_customer_secret>";
const token = "<your_token>";
// 配置云存储
const storageConfig = {
vendor: parseInt("2", 10),
region: parseInt("1", 10),
bucket: "<your_storage_bucket_name>",
accessKey: "<your_storage_access_key>",
secretKey: "<your_storage_secret_key>",
fileNamePrefix: ["quickstart"],
};
其中,
cname
值设为show
,表示加入录制实时音视频互动所在的频道。vendor
值设为2
代表使用阿里云;region
值设为1
表示CN_Shanghai
。如果你使用其他云存储厂商,参考第三方云存储地区说明赋值。
创建并初始化 Client
在 src/index.ts
中,添加如下代码,创建并初始化 Client。
const credential = new BasicCredential(username, password);
const cloudRecordingClient = new CloudRecordingClient({
appId,
credential,
// Specify the region where the server is located.
// Optional values are CN, US, EU, AP, and the client will automatically
// switch to use the best domain name according to the configured region
domainArea: DomainArea.CN,
});
申请录制服务器资源
调用 acquire
申请录制服务器资源。
// Acquire resource
let acquireResourceRes: AcquireResourceRes;
try {
acquireResourceRes = await cloudRecordingClient
.mixScenario()
.acquire(cname, uid, {
resourceExpiredHour: 1,
});
} catch (error) {
console.error("Failed to acquire resource", error);
return;
}
if (!acquireResourceRes.resourceId) {
console.error("Resource ID is missing");
return;
}
console.info(
`acquire resource success,res:${JSON.stringify(acquireResourceRes)}`
);
开始录制
获取资源后,调用 start
开始录制:
// Start resource
let startResourceRes: StartResourceRes;
try {
startResourceRes = await cloudRecordingClient
.mixScenario()
.start(cname, uid, acquireResourceRes.resourceId, {
token,
recordingConfig: {
channelType: RecordingRequestChannelTypeEnum.Live,
},
recordingFileConfig: {
avFileType: ["hls", "mp4"],
},
storageConfig,
});
} catch (error) {
console.error("Failed to start resource", error);
return;
}
if (!startResourceRes.sid) {
console.error("SID is missing");
return;
}
console.info(
`start resource success,res:${JSON.stringify(startResourceRes)}`
);
停止录制
完成录制后,调用 stop
结束录制。
// Stop resource
let stopResourceRes: StopResourceRes;
try {
stopResourceRes = await cloudRecordingClient
.mixScenario()
.stop(
cname,
uid,
acquireResourceRes.resourceId,
startResourceRes.sid,
true
);
} catch (error) {
console.error("Failed to stop resource", error);
return;
}
console.info(`stop resource success,res:${JSON.stringify(stopResourceRes)}`);
完整示例代码
src/index.ts
文件中完整代码如下,你可以自行比对:
import {
DomainArea,
BasicCredential,
CloudRecordingClient,
AcquireResourceRes,
StartResourceRes,
RecordingRequestChannelTypeEnum,
QueryMixHLSAndMP4RecordingResourceRes,
StopResourceRes,
} from "agora-rest-client";
const appId = "<your_app_id>";
const cname = "show";
const uid = "123";
const username = "<your_customer_id>";
const password = "<yout_customer_secret>";
const token = "<your_token>";
if (!appId || !cname || !uid || !username || !password) {
console.error("Required variables are missing");
process.exit(1);
}
const credential = new BasicCredential(username, password);
const storageConfig = {
vendor: parseInt("2", 10),
region: parseInt("1", 10),
bucket: "<your_storage_bucket_name>",
accessKey: "<your_storage_access_key>",
secretKey: "<your_storage_secret_key>",
fileNamePrefix: ["quickstart"],
};
if (
!storageConfig.vendor ||
!storageConfig.region ||
!storageConfig.bucket ||
!storageConfig.accessKey ||
!storageConfig.secretKey
) {
console.error("Required storage configuration variables are missing");
process.exit(1);
}
(async (): Promise<void> => {
const cloudRecordingClient = new CloudRecordingClient({
appId,
credential,
// Specify the region where the server is located.
// Optional values are CN, US, EU, AP, and the client will automatically
// switch to use the best domain name according to the configured region
domainArea: DomainArea.CN,
});
// Acquire resource
let acquireResourceRes: AcquireResourceRes;
try {
acquireResourceRes = await cloudRecordingClient
.mixScenario()
.acquire(cname, uid, {
resourceExpiredHour: 1,
});
} catch (error) {
console.error("Failed to acquire resource", error);
return;
}
if (!acquireResourceRes.resourceId) {
console.error("Resource ID is missing");
return;
}
console.info(
`acquire resource success,res:${JSON.stringify(acquireResourceRes)}`
);
// Start resource
let startResourceRes: StartResourceRes;
try {
startResourceRes = await cloudRecordingClient
.mixScenario()
.start(cname, uid, acquireResourceRes.resourceId, {
token,
recordingConfig: {
channelType: RecordingRequestChannelTypeEnum.Live,
},
recordingFileConfig: {
avFileType: ["hls", "mp4"],
},
storageConfig,
});
} catch (error) {
console.error("Failed to start resource", error);
return;
}
if (!startResourceRes.sid) {
console.error("SID is missing");
return;
}
console.info(
`start resource success,res:${JSON.stringify(startResourceRes)}`
);
await new Promise((resolve) => setTimeout(resolve, 10000));
// Query resource
let queryResourceRes: QueryMixHLSAndMP4RecordingResourceRes;
try {
queryResourceRes = await cloudRecordingClient
.mixScenario()
.queryHLSAndMP4(acquireResourceRes.resourceId, startResourceRes.sid);
} catch (error) {
console.error("Failed to query resource", error);
return;
}
if (!queryResourceRes.serverResponse) {
console.error("Query resource serverResponse is missing");
return;
}
console.info(
`query resource success,res:${JSON.stringify(queryResourceRes)}`
);
await new Promise((resolve) => setTimeout(resolve, 3000));
// Stop resource
let stopResourceRes: StopResourceRes;
try {
stopResourceRes = await cloudRecordingClient
.mixScenario()
.stop(
cname,
uid,
acquireResourceRes.resourceId,
startResourceRes.sid,
true
);
} catch (error) {
console.error("Failed to stop resource", error);
return;
}
console.info(`stop resource success,res:${JSON.stringify(stopResourceRes)}`);
})();
查看录制文件
参考第三方云存储官方文档操作,查看云存储中的文件。根据 start
方法中 storageconfig.fileNamePrefix
参数的设置,录制生成文件位于 quickstart
目录下。如下截图以阿里云为例:
录制过程中生成了 .m3u8
、.ts
和 mp4
文件。最终 quickstart
文件夹内包含如下:
.
├── <sid>_testchannel.m3u8
├── <sid>_testchannel_0.mp4
└── <sid>_testchannel_20241119070642248.ts
根据你在 start
方法中设置的录制时间,云存储中可能会有多个 .ts 文件。录制文件介绍请参考录制文件命名和切片规则。
进阶设置
本文提供的示例代码仅为快速上手时使用。在实际业务中,你需要根据实际业务场景修改请求参数。如下示例代码展示了录制直播频道中所有的音视频流,且输出的视频流需要转码的参数配置:
startResourceRes = await cloudRecordingClient
.mixeScenario()
.start(cname, uid, acquireResourceRes.resourceId, {
token,
recordingConfig: {
channelType: RecordingRequestChannelTypeEnum.Live,
streamTypes: 2,
maxIdleTime: 30,
audioProfile: 2,
transcodingConfig: {
width: 640,
height: 480,
fps: 15,
bitrate: 800,
mixedVideoLayout: 0,
backgroundColor: "#000000",
},
subscribeAudioUids: ["#allstream#"],
subscribeVideoUids: ["#allstream#"],
},
recordingFileConfig: {
avFileType: ["hls", "mp4"],
},
storageConfig,
});
参考信息
示例项目
完成快速开始后,你可以体验更多云端录制示例: