快速开始-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>
可以看到 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 列表提问,我们会尽快为您解答。