快速开始-Track 模式
如果您已经完成了上文中提到的开发准备,现在就让我们开始编写一个基本的实时音视频应用吧。这个应用会展示 SDK 基本的连麦功能,包括 加入房间,采集,发布,订阅 等过程。但是在实际的连麦应用开发过程中,是需要后端介入的(用于给用户鉴权并生成 RoomToken),在这里为了减少开发的时间,请您先预先生成 2 个 RoomToken(要求同一个 APPID,同一个 RoomName,不同的 UserName)。
如果您还不知道如何生成 RoomToken,请先阅读 七牛实时音视频云接入指南
准备好之后,请将下文代码中的 ROOMTOKEN_1 和 ROOMTOKEN_2 替换成您生成的 RoomToken。
我们将会编写 2 个独立的 html 页面,其中第一个页面负责采集本地的摄像头/麦克风数据并将其发布,第二个页面负责订阅刚刚发布的这个流并将其展示在页面上。
注意,这里是以 Track 模式编写的 Demo,如果您还不清楚 Stream 模式和 Track 模式的区别,请先阅读 模式选择
因为使用了 Track 模式,所以这里我们假想一个 Track 模式的一个常用场景,即同时发布自己的屏幕采集和摄像头采集(当然还包括麦克风)。因为这里涉及到使用屏幕采集功能,请先确认您已经安装好了屏幕采集插件。
您可以从这里下载到这个 Demo 最终的成品代码,但是还是建议您在按照步骤阅读完之后再参阅。
引入 SDK 并准备页面
如果一切准备就绪,让我们开始吧,首先准备一个空文件夹作为我们项目的根目录。参考上文 引入 SDK 中的方式,下载 pili-rtc-web.js 的最新代码到我们项目的目录下。
然后分别创建 publish.html 和 subscribe.html 2 个文件,作为我们的 2 个页面。
<!-- publish.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Publish Page</title>
</head>
<body>
<p>本地音视频轨</p>
<div id="localtracks" style="width: 640px;"></div>
<button onclick="joinRoom()">加入房间</button>
<!-- 这里引入我们的 SDK -->
<script src="./pili-rtc-web.js"></script>
<script>
// 确认引入成功
console.log("current version", QNRTC.version);
</script>
</body>
</html>
<!-- subscribe.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Subscribe Page</title>
</head>
<body>
<p>远端音视频轨</p>
<div id="remotetracks" style="width: 640px;"></div>
<button onclick="joinRoom()">加入房间</button>
<!-- 同样,这里引入我们的 SDK -->
<script src="./pili-rtc-web.js"></script>
<script>
// 确认引入成功
console.log("current version", QNRTC.version);
</script>
</body>
</html>
SDK 会在被引入的时刻自动操作一些浏览器的全局 DOM 对象,所以请在
<body>标签内引入 SDK
可以看到 2 个页面的基本结构都是一样的,一个用来加入房间的按钮和一个准备用来播放媒体流的空元素。在正式开发过程中我们可以根据需要将订阅和发布放到一个页面上,这里为了阐述方便采用了这种模式。
然后在项目的目录下起一个 http 服务,打开 Chrome 通过 localhost 或者 127.0.0.1 访问刚刚那 2 个页面(因为屏幕和摄像头采集只能在 localhost 或者 https 下完成), 观察到控制台打印出了 sdk 的版本信息说明这一步完成了。
如果您不知道如何在本地起一个
http服务,推荐使用基于 nodejs 的 http-server。可以参考这篇文章。
注意!Chrome 只允许
localhost或者https的网络页面访问媒体设备。请在之后的开发和上线中注意这一点
加入房间
上文提到过,SDK 所有的功能都是从 RoomToken 开始的,所以加入房间只需要将 RoomToken 作为参数传给 SDK 就可以了。编辑 publish.html 在 <script> 中添加如下代码。
// 确认引入成功
console.log("current version", QNRTC.version);
// 这里采用的是 async/await 的异步方案,您也可以根据需要或者习惯替换成 Promise 的写法
async function joinRoom() {
// 初始化一个房间 Session 对象, 这里使用 Track 模式
const myRoom = new QNRTC.TrackModeSession();
// 这里替换成刚刚生成的 RoomToken
await myRoom.joinRoomWithToken(ROOMTOKEN_1);
console.log("joinRoom success!");
}
同样,在 subscribe.html 中也添加一样的代码,不过注意要把 ROOMTOKEN_1 替换成 ROOMTOKEN_2。
再次访问这 2 个页面,分别点击加入房间按钮,看到控制台打印出 joinRoom success! 表示这一步完成。
采集并发布本地的音视频轨
接下来,我们的任务是在 publish.html 下采集本地的媒体数据并把它发布到房间中,供下文的 subscribe.html 去订阅。在编写代码之前,请先确认您的电脑已经连接了摄像头和麦克风设备(支持 USB 设备)。
如果没有摄像头或者没有麦克风(缺少其中一个),请稍微修改下文中的采集参数关闭音频或者视频采集
这里我们将要采集 3 个音视频轨,分别是 摄像头视频轨、屏幕共享视频轨 和 麦克风音频轨。
编辑 publish.html 中的 <script>,添加如下代码。
// 增加一个函数 publish,用于采集并发布自己的媒体流
// 这里的参数 myRoom 是指刚刚加入房间时初始化的 Session 对象
async function publish(myRoom) {
// 我们打开了 3 个参数,即采集音频,采集视频,采集屏幕共享。
// 这个函数会返回一个列表,列表中每一项就是一个音视频轨对象
const localTracks = await QNRTC.deviceManager.getLocalTracks({
audio: { enabled: true, tag: "audio" },
video: { enabled: true, tag: "video" },
screen: { enabled: true, tag: "screen" },
});
console.log("my local tracks", localTracks);
// 将刚刚的 Track 列表发布到房间中
await myRoom.publish(localTracks);
console.log("publish success!");
}
这个函数很简单,总共就 2 个语句,我们现在仔细分析一下这 2 行代码分别做了什么。
第一句,调用了 SDK 提供的方法 getLocalTracks,从名字我们可以看出,这个方法负责采集本地的音视频轨,并将其包装成 SDK 的 Track 对象并以列表的形式返回给我我们。这里的调用参数中的 audio、video、screen 分别指打开麦克风采集、打开摄像头采集、打开屏幕采集。这里的 tag 是一个自定义的参数,每一个返回的 Track 对象都会打上这里提供的 tag 用于给用户区分。可以通过如下方法查看一个 Track 的 tag。
const localTracks = await QNRTC.deviceManager.getLocalTracks({
audio: { enabled: true, tag: "audio" },
video: { enabled: true, tag: "video" },
screen: { enabled: true, tag: "screen" },
});
console.log("track 1 tag is", localTracks[0].info.track);
console.log("track 2 tag is", localTracks[1].info.track);
console.log("track 3 tag is", localTracks[2].info.track);
getLocalTracks 不保证返回的 Track 顺序,tag 是区分 Track 的唯一方式,实际开发中请根据自己的需要使用。
然后在刚刚 joinRoom 的函数末尾调用我们刚刚编写的 publish
async function joinRoom() {
// 初始化一个房间 Session 对象
const myRoom = new QNRTC.TrackModeSession();
// 这里替换成刚刚生成的 RoomToken
await myRoom.joinRoomWithToken(ROOMTOKEN_1);
console.log("joinRoom success!");
await publish(myRoom);
}
现在访问页面,加入房间后 Chrome 应该会申请媒体设备的权限以及选择屏幕共享的窗口,同意之后观察控制台打印出 publish success! 表示发布成功。
如果您有需要想要看到自己刚刚采集的视频画面,可以在 publish 函数的末尾添加如下代码
...
...
await myRTC.publish(localTracks);
console.log("publish success!");
// 在这里添加
// 获取页面上的一个元素作为播放画面的父元素
const localElement = document.getElementById("localtracks");
// 遍历本地采集的 Track 对象
for (const localTrack of localTracks) {
// 如果这是麦克风采集的音频 Track,我们就不播放它。
if (localTrack.info.tag === "audio") continue;
// 调用 Track 对象的 play 方法在这个元素下播放视频轨
localTrack.play(localElement, true);
}
下一步,我们将在 subscribe.html 中完成订阅的代码,实现对我们刚刚发布的音视频轨道的订阅。
订阅远端发布的音视频轨
注意,在 Track 模式下,订阅的目标不再是用户了,因为从上文发布的过程中我们可以知道,一个用户可以发布多个 Track,而在实际需求中我们不一定会把他所有发布的 Track 都订阅(可能只订阅音频之类的),所以在 Track 模式下,订阅的目标是以 Track 为单位的。
那么,我怎么知道房间中有哪些 Track,这些 Track 又分别对应哪些用户呢?这就是我们接下来讲述的重点。
我们将这些 Track 的信息称为 TrackInfo, TrackInfo 会包含除了媒体数据以外所有和这个 Track 相关的信息。所以订阅操作的本质就是通过读取 TrackInfo 然后返回包含了媒体数据的 Track 对象。
实际的订阅操作中只需要传入
trackId,trackId是一个房间中所有TrackInfo的唯一标识,通过TrackInfo.trackId即可获取
为了方便不同需求的用户组织代码,SDK 一共暴露了 2 个入口获取当前房间的 TrackInfo:
myRTC.trackInfoList加入房间成功后,就可以通过访问这个值获取房间当前所有的TrackInfo(这里的 TrackInfo 指除自己以外的其他人的)myRTC.users加入房间成功后,可以通过这个值获取当前房间的用户列表(包括自己),每个用户对象都可以通过访问他的publishedTrackInfo来获取这个用户已经发布的TrackInfo。
上面的 myRTC 是我们刚刚实例化的 Session 对象。可以看到,如果您的业务逻辑需要对具体用户做具体操作,在获取 Track 信息时通过 users 来获取会是一个很好的选择。如果您觉得这样获取比较麻烦,直接访问 trackInfoList 也可以拿到您想要的信息。
获取 TrackInfo 的方法知道了,那么我们又怎么知道远端在什么时候发布或者取消发布了 Track 呢?
这里,SDK 会通过 事件 来通知,您只需要确认在 事件 触发之前 监听 其就好。
比如当一个远端用户发布了自己的 Track,此时在我们这边就会触发 track-add 事件,事件会带上远端所发布的 TrackInfo。而且,事件触发时,SDK 的 trackInfoList 以及 users 也都会自动更新,重新获取一次就能看到最新的 TrackInfo。
具体让我们通过代码来理解,在 subscribe.html 的 <script> 中添加函数 subscribe 和 autoSubscribe, subscribe 用于传入 TrackInfo 完成订阅操作并播放订阅流,autoSubscribe 用于实时检测房间的 TrackInfo 变化并在合适的时机调用 subscribe
// 这里的参数 myRoom 是指刚刚加入房间时初始化的 Session 对象, 同上
// trackInfoList 是一个 trackInfo 的列表,订阅支持多个 track 同时订阅。
async function subscribe(myRoom, trackInfoList) {
// 通过传入 trackId 调用订阅方法发起订阅,成功会返回相应的 Track 对象,也就是远端的 Track 列表了
const remoteTracks = await myRoom.subscribe(trackInfoList.map(info => info.trackId));
// 选择页面上的一个元素作为父元素,播放远端的音视频轨
const remoteElement = document.getElementById("remotetracks");
// 遍历返回的远端 Track,调用 play 方法完成在页面上的播放
for (const remoteTrack of remoteTracks) {
remoteTrack.play(remoteElement);
}
}
在一次批量订阅中,如果想区分返回的 Track(比如播放屏幕分享的 Track 时给更大的界面),和上文一样,通过 tag 来区分。这里的 tag 来自于远端采集它的时候所指定的 tag。
代码不多,总共就两步。先是发起订阅拿到返回的 Track 对象,之后调用 Track 的 play 方法在页面上播放。
下面我们实现 autoSubscribe 将 TrackInfo 传给 subscribe。
// 这里的参数 myRoom 是指刚刚加入房间时初始化的 Session 对象, 同上
function autoSubscribe(myRoom) {
const trackInfoList = myRoom.trackInfoList;
console.log("room current trackInfo list", trackInfoList)
// 调用我们刚刚编写的 subscribe 方法
// 注意这里我们没有使用 async/await,而是使用了 Promise,大家可以思考一下为什么
subscribe(myRoom, trackInfoList)
.then(() => console.log("subscribe success!"))
.catch(e => console.error("subscribe error", e));
// 添加事件监听,当房间中出现新的 Track 时就会触发,参数是 trackInfo 列表
myRoom.on("track-add", (trackInfoList) => {
console.log("get track-add event!", trackInfoList);
subscribe(myRoom, trackInfoList)
.then(() => console.log("subscribe success!"))
.catch(e => console.error("subscribe error", e));
});
// 就是这样,就像监听 DOM 事件一样通过 on 方法监听相应的事件并给出处理函数即可
}
最后,让我们在 subscribe.html 的 joinRoom 中调用 autoSubscribe 即可
async function joinRoom() {
// 初始化一个房间 Session 对象
const myRoom = new QNRTC.TrackModeSession();
// 这里替换成刚刚生成的 RoomToken
await myRoom.joinRoomWithToken(ROOMTOKEN_2);
console.log("joinRoom success!");
// 在这里添加
autoSubscribe(myRoom);
}
好啦,这个时候再访问这 2 个页面,分别进入房间中,在 subscribe.html 中看到你自己和屏幕的画面就代表成功啦。您成功完成了一个最简单的一对一连麦应用(而且还支持单用户多流)。
接下来...
经过上面的步骤之后,相信您已经对一次连麦互动的流程以及 SDK 的使用有了大概的了解。如果您已经准备正式开始接入我们的 SDK,下面一些建议可以让您更加熟悉 SDK。
- 这个 Demo 是用 2 个页面完成的,一个页面负责发布一个负责订阅。尝试将它整合成一个页面(比如通过按钮来选择以哪个用户的身份加入房间)
- SDK 使用中每个功能的细节都可以通过左侧的功能列表去查看
- 如果您想了解 SDK 的架构以及所暴露的模块细节,可以阅读右上角的 API 文档
如果您在使用过程中遇到什么疑问,或者遇到了 BUG,请在 Github 的 issue 列表提问,我们会尽快为您解答。